To compare the performance of varKode to Skmer, we will use leave-one-out cross validation: we remove one sample from the dataset, train a varKode model or make a skmer reference with the remaining samples, and then use the sample left out as query. We then record whether or not we correctly identify this sample in varKoder, and whether or not the closest sample with Skmer has the same identification.

For traditional barcodes, we assembled the genome of each sample, and then used BLAST to search for each of the traditional barcode genes. We recorded if we could find this gene in the assembly, coding as missing data if we could not. We then recorded whether the best BLAST hit for a sample was the correct species.

#rm(list=ls())
library(tidyverse)
── Attaching core tidyverse packages ───────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     ── Conflicts ─────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(future)
library(ggthemes)
library(patchwork)
library(cowplot)

Attaching package: ‘cowplot’

The following object is masked from ‘package:patchwork’:

    align_plots

The following object is masked from ‘package:ggthemes’:

    theme_map

The following object is masked from ‘package:lubridate’:

    stamp
library(patchwork)
library(phytools)
Loading required package: ape

Attaching package: ‘ape’

The following object is masked from ‘package:dplyr’:

    where

Loading required package: maps

Attaching package: ‘maps’

The following object is masked from ‘package:purrr’:

    map
library(ape)
library(furrr)
set.seed(14164)

#function to prevent overwriting plots
safe_ggsave <- function(filename, ...) {
  if (file.exists(filename)) {
    warning(paste("File", filename, "already exists. File not overwritten."))
    return(invisible(NULL))
  }
  ggsave(filename, ...)
}

VarKoder

For VarKoder, we used leave-one-out cross-validation to test the accuracy for family, genera, species in the joint Malpighiaceae-Chrysobalanaceae dataset. We used as input data varKodes produced from kmers of size 7 and 500Kbp to 200Mbp of data, or all of the data available if less than 200 Mbp. For each sample, we built a model using as input data from all other samples. Then we queried the sample left out, using as input the images generated from 500Kb to the total data available. Now we will summarize the results.

Accuracy vs data amount and taxonomic levels

In this test, we used varKoder v0.8.0. Let’s process the results.

read_and_process_xval = function(infolder){
  plan(multisession(workers = 12))
varkoder_results = list.files(infolder,
                                      'predictions.csv',
                                      recursive=T,
                                      full.names = T) %>%
  furrr::future_map_dfr(~read_csv(.x) %>% mutate(sample_id = as.character(sample_id))) %>% 
  select(-1) %>%
  mutate(query_basepairs = case_when(
    is.character(query_basepairs) ~ as.numeric(str_remove(query_basepairs, "K")) * 1000,
    TRUE ~ as.numeric(query_basepairs)
  )) %>%
  filter(str_detect(format(query_basepairs, scientific = FALSE,trim = TRUE), "^[125]0+$")) %>% #we will ignore queries that are not standardized sizes
  rename(query_bp = query_basepairs) %>%
  mutate(quality_included = T)
plan(sequential)

all_taxlabels = str_remove(varkoder_results$actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist %>% unique

varkoder_results = varkoder_results %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';'),
         predicted_list = str_split(predicted_labels,';')
         ) %>%
  rowwise() %>%
  mutate(family_correct = query_labels[str_detect(query_labels,'family')] %in% predicted_list,
         genus_correct = query_labels[str_detect(query_labels,'genus')] %in% predicted_list,
         species_correct = ifelse(any(str_detect(query_labels,'species')),
                                  query_labels[str_detect(query_labels,'species')] %in% predicted_list,
                                  NA
                                  ),
         family_incorrect = any(!(predicted_list[str_detect(predicted_list,'family')] %in% query_labels[str_detect(query_labels,'family')])),
         genus_incorrect = any(!(predicted_list[str_detect(predicted_list,'genus')] %in% query_labels[str_detect(query_labels,'genus')])),
         species_incorrect = ifelse(any(str_detect(query_labels,'species')),
                                  any(!(predicted_list[str_detect(predicted_list,'species')] %in% query_labels[str_detect(query_labels,'species')])),
                                  NA
                                  )
         
         )

return(varkoder_results)
}
summarize_results = function(res,level){
  res = res %>%
    ungroup() %>%
    mutate(low_quality = str_detect(actual_labels,"low_quality:True"),
           result = as.character(ifelse(res[,str_c(level,'correct',sep='_')] & !res[,str_c(level,'incorrect',sep='_')], 'correct',
                           ifelse(res[,str_c(level,'correct',sep='_')] & res[,str_c(level,'incorrect',sep='_')], 'ambiguous',
                                  ifelse(!res[,str_c(level,'correct',sep='_')]  & res[,str_c(level,'incorrect',sep='_')], 'incorrect',
                                                 'inconclusive'
                                  ))))
           ) %>%
    filter(!is.na(result)) %>%
    group_by(query_bp,result) %>%
    summarise(N=n(), .groups = 'drop') %>%
    group_by(query_bp) %>%
    mutate(p= N/sum(N)) %>%
    #mutate(query_bp = as.integer(str_remove(query_bp,'K'))*1000) %>%
    ungroup() %>%
    mutate(query_bp = as.factor(query_bp)) %>%
    complete(query_bp,result, fill = list(p = 0, N = 0)) %>%
    mutate(query_bp = as.numeric(as.character(query_bp))) %>%
    ungroup()
    
  return(res)
}
plot_area = function(sum_df, title, relative = FALSE, grid = TRUE, xlim_all = TRUE, wrap){
  breaks = c(500000,
             1000000,
             2000000,
             5000000,
             10000000,
             20000000,
             50000000,
             100000000,
             200000000
             )
  if (xlim_all){
    xlimits = range(breaks)
  } else {
    xlimits = range(sum_df$query_bp)
  }
  
  
  sum_df = sum_df %>%
    mutate(result = factor(result,ordered = T, levels = c('correct','ambiguous','inconclusive','incorrect'))) 
  if (relative){
    ylimits = c(0,1)
  } else {
    ylimits = c(0,sum_df %>% group_by(query_bp) %>% summarize(N=sum(N)) %>% pull(N) %>% max)
  }
  
  
  # Get colors from a Color Brewer palette
  brewer_colors <- RColorBrewer::brewer.pal(4, "Accent")
  
  if (relative) {
    p1 = ggplot(sum_df, aes(x=query_bp,y=p,fill=result)) +
    geom_area(position='stack') +
    scale_fill_manual(values = setNames(brewer_colors, c("correct", "ambiguous", "inconclusive", "incorrect"))) +
    scale_alpha_manual(values=c(0.5,1)) +
    scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = breaks)  +
    scale_y_continuous() +
    ggtitle(title) +
    ylab('Fraction of samples') +
    xlab('Base pairs in query images') +
    theme_few() +
    theme(axis.text.x = element_text(hjust=1,angle=45))
  } else {
      p1 = ggplot(sum_df, aes(x=query_bp,y=N,fill=result)) +
    geom_area(position='stack') +
    scale_fill_manual(values = setNames(brewer_colors, c("correct", "ambiguous", "inconclusive", "incorrect"))) +
    scale_alpha_manual(values=c(0.5,1)) +
    scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = breaks)   +
    scale_y_continuous() +
    ggtitle(title) +
    ylab('Number of samples') +
    xlab('Base pairs in query images') +
    theme_few() +
    theme(axis.text.x = element_text(hjust=1,angle=45))
  }
  
  if (grid){
    p1 = p1 +
      scale_y_continuous(n.breaks = 10, minor_breaks = waiver()) +
      theme(panel.background = element_rect(fill = NA),
            panel.grid.major.y = element_line(colour = gray(0.5)),
            panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
            panel.ontop = TRUE)
  }
  
  p1 = p1 + coord_cartesian(xlim=xlimits, ylim=ylimits,expand = FALSE)
  
  if (!missing(wrap)) {
    p1 = p1 + facet_wrap(as.formula(wrap))
  }
  
  return(p1)
}
  

Now let’s plot genus-level accuracy:

results = read_and_process_xval('additional_tests_cgr/results_cgr_varKoder_vit_large_patch32_224/')
summary_genus = summarize_results(results,'genus')
p_genus = plot_area(summary_genus, 'varKoder genus', relative = TRUE)
p_genus

Now the same but with species

summary_species = summarize_results(results,'species')
p_species = plot_area(summary_species, 'varKoder species', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_species

Finally, family

summary_family = summarize_results(results,'family')
p_family = plot_area(summary_family, 'varKoder family', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_family

what explains the errors?

Now we will try to identify which samples failed and why they failed. Particuarly, how do DNA quality, amount of data, and the number of samples per class impact results? We will use genus-level predictions to test.

genus_predictions = results %>%
  mutate(predicted_genus = str_extract(predicted_labels, 'genus:[^;]*'),
         actual_genus = str_extract(actual_labels, 'genus:[^;]*')) %>%
  select(-starts_with('family'),-starts_with('species')) %>%
  pivot_longer(cols = starts_with("genus"), names_to = "predicted_label", values_to = "confidence") %>%
  filter(actual_genus == predicted_label) %>%
  select(query_bp, sample_id, basefrequency_sd, actual_genus, confidence) %>%
  mutate(query_bp = 1000*(str_remove(query_bp, "K") %>% as.integer))

genus_predictions = genus_predictions %>%
  select(sample_id, actual_genus) %>%
  distinct() %>%
  group_by(actual_genus) %>%
  summarise(N_samples = n()) %>%
  right_join(genus_predictions)
Joining with `by = join_by(actual_genus)`
genus_predictions %>% arrange(N_samples)

Now let’s make some plots. First, what is the effect of number of samples per class in confidence?

set.seed(13214526)
plot_genus_N_vs_conf = ggplot(genus_predictions, aes(x = N_samples-1, 
                              y = confidence)) + 
  scale_color_viridis_c() +
  geom_jitter(alpha=0.3) + 
  scale_x_log10() +
  #ylab('Confidence in correct prediction\n(logit scale)') +
  ylab('Confidence in correct genus prediction') +
  xlab('Number of training samples in correct genus\n(log scale)') +
  #scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
  scale_y_continuous(limits=c(0,1)) +
  theme_few() +
  theme(panel.grid.major.y = element_line(colour = gray(0.8)))

plot_genus_N_vs_conf

Now, what is the effect of sample quality in confidence?

set.seed(13214526)
plot_genus_freqsd_vs_conf = ggplot(genus_predictions, aes(x = basefrequency_sd, y = confidence)) + 
  geom_point(alpha=0.3) + 
  scale_x_log10() +
  #scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
  scale_y_continuous(limits=c(0,1)) +
  #ylab('Confidence in correct prediction\n(logit scale)') +
  ylab('Confidence in correct genus prediction') +
  xlab('Standard deviation of base frequencies') +
  theme_few() +
  theme(panel.grid.major.y = element_line(colour = gray(0.8)))

plot_genus_freqsd_vs_conf

Now, what is the effect of amount of data in confidence?

set.seed(13214526)
plot_genus_bp_vs_conf = ggplot(genus_predictions, aes(x = query_bp, y = confidence)) + 
  geom_jitter(alpha=0.3) + 
  #scale_y_continuous(trans = "logit", breaks = c(1e-4,0.001,0.01,0.1,0.25,0.5,0.75,0.9,0.99,0.999,1-1e-4)) +
  scale_y_continuous(limits=c(0,1)) +
  #ylab('Confidence in correct prediction\n(logit scale)') +
  ylab('Confidence in correct genus prediction') +
  xlab('Base pairs in query images\n(log scale)') +
  scale_x_log10() +
  theme_few() +
  theme(panel.grid.major.y = element_line(colour = gray(0.8)))

plot_genus_bp_vs_conf

Let’s put it all together now in a linear model. We remove one from N_samples since one sample is used for validation in the leave-one-out cross-validation that we did.

lm_data = genus_predictions %>%
  mutate(confidence = ifelse(confidence == 1, confidence-0.0000001, confidence),
         confidence = car::logit(confidence)) %>%
  mutate(query_bp = (query_bp - mean(query_bp))/sd(query_bp),
         basefrequency_sd = (basefrequency_sd - mean(basefrequency_sd))/sd(basefrequency_sd),
         N_samples = ((N_samples-1) - mean(N_samples-1))/sd(N_samples-1)
         ) 

full_model = lm(formula = confidence~query_bp*basefrequency_sd*N_samples, data = lm_data) 
full_model

Call:
lm(formula = confidence ~ query_bp * basefrequency_sd * N_samples, 
    data = lm_data)

Coefficients:
                        (Intercept)                             query_bp  
                           4.488259                             0.125846  
                   basefrequency_sd                            N_samples  
                          -0.553064                             1.662176  
          query_bp:basefrequency_sd                   query_bp:N_samples  
                           0.204618                            -0.005384  
         basefrequency_sd:N_samples  query_bp:basefrequency_sd:N_samples  
                          -0.103651                            -0.037260  
reduced_model = step(full_model,direction = 'both')
Start:  AIC=2660.3
confidence ~ query_bp * basefrequency_sd * N_samples

                                      Df Sum of Sq    RSS    AIC
- query_bp:basefrequency_sd:N_samples  1   0.13619 7282.7 2658.3
<none>                                             7282.6 2660.3

Step:  AIC=2658.35
confidence ~ query_bp + basefrequency_sd + N_samples + query_bp:basefrequency_sd + 
    query_bp:N_samples + basefrequency_sd:N_samples

                                      Df Sum of Sq    RSS    AIC
- query_bp:N_samples                   1    0.0505 7282.8 2656.4
<none>                                             7282.7 2658.3
- basefrequency_sd:N_samples           1    6.7172 7289.4 2658.4
+ query_bp:basefrequency_sd:N_samples  1    0.1362 7282.6 2660.3
- query_bp:basefrequency_sd            1   14.7993 7297.5 2660.9

Step:  AIC=2656.36
confidence ~ query_bp + basefrequency_sd + N_samples + query_bp:basefrequency_sd + 
    basefrequency_sd:N_samples

                             Df Sum of Sq    RSS    AIC
<none>                                    7282.8 2656.4
- basefrequency_sd:N_samples  1    6.8663 7289.6 2656.5
+ query_bp:N_samples          1    0.0505 7282.7 2658.3
- query_bp:basefrequency_sd   1   14.8391 7297.6 2659.0
summary(reduced_model)

Call:
lm(formula = confidence ~ query_bp + basefrequency_sd + N_samples + 
    query_bp:basefrequency_sd + basefrequency_sd:N_samples, data = lm_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-9.5644 -0.9384  0.2772  1.1785  5.9867 

Coefficients:
                           Estimate Std. Error t value Pr(>|t|)    
(Intercept)                 4.49197    0.04065 110.510  < 2e-16 ***
query_bp                    0.13430    0.04627   2.903  0.00373 ** 
basefrequency_sd           -0.54313    0.06556  -8.285  < 2e-16 ***
N_samples                   1.66672    0.04017  41.496  < 2e-16 ***
query_bp:basefrequency_sd   0.22549    0.10524   2.143  0.03225 *  
basefrequency_sd:N_samples -0.08782    0.06025  -1.457  0.14513    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.798 on 2253 degrees of freedom
Multiple R-squared:  0.5226,    Adjusted R-squared:  0.5215 
F-statistic: 493.2 on 5 and 2253 DF,  p-value: < 2.2e-16
plot(reduced_model)


broom::tidy(reduced_model) %>% write_csv('lm_table.csv')

Now let’s save the three of them as a single plot using cowplot.

preds = genus_predictions %>%
  mutate(N_samples=N_samples-1) %>% 
  select(original_N = N_samples,
         original_bp = query_bp, 
         original_sd = basefrequency_sd) %>%
  mutate(query_bp = (original_bp - mean(original_bp))/sd(original_bp),
         basefrequency_sd = (original_sd - mean(original_sd))/sd(original_sd),
         N_samples = ((original_N) - mean(original_N))/sd(original_N)
         ) 

preds = bind_rows(expand.grid(query_bp=unique(preds$query_bp),
                              N_samples=0,
                              basefrequency_sd=0
                              ),
                  expand.grid(query_bp=0,
                              N_samples=unique(preds$N_samples),
                              basefrequency_sd=0
                              ),
                  expand.grid(query_bp=0,
                              N_samples=0,
                              basefrequency_sd=unique(preds$basefrequency_sd)
                              )
                  )

logistic <- function(L) {
  1 / (1 + exp(-L))
}

preds = predict(reduced_model, newdata=preds, interval='conf') %>%
  as_tibble() %>%
  mutate_all(~logistic(.x)+0.0000001) %>%
  rename(confidence=fit) %>%
  bind_cols(preds %>%
              mutate(N_samples = N_samples*sd(genus_predictions$N_samples-1)+mean(genus_predictions$N_samples-1),
                     basefrequency_sd = basefrequency_sd*sd(genus_predictions$basefrequency_sd)+
                       mean(genus_predictions$basefrequency_sd),
                     query_bp = query_bp*sd(genus_predictions$query_bp)+mean(genus_predictions$query_bp))
              )
  
combined_conf = patchwork::wrap_plots(plot_genus_N_vs_conf + 
                                        theme(text = element_text(size=8)) + 
                                        geom_line(data=(filter(preds, 
                                                               N_samples != mean(genus_predictions$N_samples-1))),
                                                  color="blue") +
                                        geom_ribbon(data=(filter(preds, 
                                                               N_samples != mean(genus_predictions$N_samples-1))),
                                                    aes(ymin=lwr, ymax=upr), 
                                                        fill="lightblue", 
                                                        alpha=0.4),
                                      plot_genus_bp_vs_conf + theme(axis.title.y=element_blank(), 
                                                                    axis.text.y=element_blank(), 
                                                                    text = element_text(size=8)) +
                                        geom_line(data=(filter(preds, 
                                                               query_bp != mean(genus_predictions$query_bp))),
                                                  color="blue") +
                                        geom_ribbon(data=(filter(preds, 
                                                               query_bp != mean(genus_predictions$query_bp))),
                                                    aes(ymin=lwr, ymax=upr), 
                                                        fill="lightblue", 
                                                        alpha=0.4),
                                      plot_genus_freqsd_vs_conf + theme(axis.title.y=element_blank(), 
                                                                        axis.text.y=element_blank(),
                                                                        text = element_text(size=8)) +
                                        geom_line(data=(filter(preds, 
                                                               basefrequency_sd != mean(genus_predictions$basefrequency_sd))),
                                                  color="blue") +
                                        geom_ribbon(data=(filter(preds, 
                                                               basefrequency_sd != mean(genus_predictions$basefrequency_sd))),
                                                    aes(ymin=lwr, ymax=upr), 
                                                        fill="lightblue", 
                                                        alpha=0.4)) +
  patchwork::plot_annotation(tag_levels = 'A',
                             title = 'Factors affecting varKode prediction accuracy',theme = theme(plot.title = element_text(hjust=0.5))) 

combined_conf


safe_ggsave (filename = 'images_manuscript/supp_conf_predictors.pdf',device = 'pdf',width = 7,height=3,units = 'in',useDingbats=F)
Warning: File images_manuscript/supp_conf_predictors.pdf already exists. File not overwritten.

Skmer

For skmer, we left each sample out, built a reference and then queried that sample. We have several files in which reference samples are ordered by their distance to the query, we here we will evaluate whether the closest sample is from the correct species or genus.

Because it is not clear how skmer behaves for different levels of coverage, we repeated this for several input sizes (in number of basepairs) as query, but always used the maximum input dize available (up to 200Mb) for references.

Let’s make a function that extracts these results as a table.


samp_labels = results %>% select(sample_id,actual_labels) %>% distinct()

extract_skmer_results = function(file_path) {
    # Read only the first 2 lines of the file
    file_lines <- readLines(file_path, n = 2)
    
    # Extract sample_ID, basepairs from the first line
    sample_info <- str_match(file_lines[1], "\\s*(.*?)@(\\d+K)")[, 2:3]
    sample_ID <- sample_info[1]
    basepairs <- sample_info[2]
    
    # Extract reference_sample_ID, distance from the second line
    reference_info <- str_match(file_lines[2], "\\s*(.*?)@.*\\s+(\\d+\\.\\d+)")[, 2:3]
    reference_sample_ID <- reference_info[1]
    distance <- as.numeric(reference_info[2])
    
    # Create a tibble
    tibble(
        sample_id = sample_ID,
        query_bp = basepairs,
        closest_reference_sample_id = reference_sample_ID,
        closest_distance = distance
    ) 
}

Now we will apply this function to all skmer output files.

plan(multisession(workers = 12))
skmer_results_df = furrr::future_map_dfr(
  list.files('Malpighiales/skmer/skmer_xval_results/', full.names = T),
  ~ extract_skmer_results(.x)
) %>%
  left_join(samp_labels, by = 'sample_id') %>%
  left_join(
    samp_labels %>% select(
      closest_reference_sample_id = 'sample_id',
      predicted_labels = actual_labels
    ),
    by = 'closest_reference_sample_id'
  ) %>%
  mutate(
    query_labels = str_remove(actual_labels, ";*low_quality:True;*") %>% str_split(';'),
    predicted_list = str_split(predicted_labels, ';')
  ) %>%
  rowwise() %>%
  mutate(
    family_correct = query_labels[str_detect(query_labels, 'family')] %in% predicted_list,
    genus_correct = query_labels[str_detect(query_labels, 'genus')] %in% predicted_list,
    species_correct = ifelse(any(str_detect(
      query_labels, 'species'
    )),
    query_labels[str_detect(query_labels, 'species')] %in% predicted_list,
    NA),
    family_incorrect = any(!(predicted_list[str_detect(predicted_list, 'family')] %in% query_labels[str_detect(query_labels, 'family')])),
    genus_incorrect = any(!(predicted_list[str_detect(predicted_list, 'genus')] %in% query_labels[str_detect(query_labels, 'genus')])),
    species_incorrect = ifelse(any(str_detect(
      query_labels, 'species'
    )),
    any(!(
      predicted_list[str_detect(predicted_list, 'species')] %in% query_labels[str_detect(query_labels, 'species')]
    )),
    NA),
    query_bp = as.numeric(str_remove(query_bp, "K")) * 1000
    
  ) 
plan(sequential)
skmer_results_df

Now let’s summarize and plot by genus:

skmer_summary_genus = summarize_results(skmer_results_df,'genus')
p_skmer_genus = plot_area(skmer_summary_genus, 'Skmer genus', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_genus

Now by species. In Skmer, there is no inconclusive result: if there is no correct species prediction, it means that a sample was predicted in the wrong genus and therefore it is incorrect

skmer_summary_species = summarize_results(skmer_results_df,'species') %>%
  mutate(result = ifelse(result == 'correct', 'correct','incorrect')) %>%
  group_by(query_bp,result) %>%
  summarise_all(sum)
p_skmer_species = plot_area(skmer_summary_species, 'Skmer species', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_species

And now by family:

skmer_summary_family = summarize_results(skmer_results_df,'family')
skmer_summary_family 
p_skmer_family = plot_area(skmer_summary_family, 'Skmer family', relative = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_skmer_family

Traditional barcodes

BLAST single gene

Let’s now read the traditional barcode BLAST results and summarize them in the same way as skmer and varKoder. Let’s start by defining a fuction that reads the data so we can summarize it using the previously defined functions.

read_traditional_barcodes = function(bp) {
  input_file = paste0(
    'Malpighiales/traditional_barcodes/2_blast_phylogeny_result/Genus/',
    bp,
    'M_blast_phylo_sum_sp.tsv'
  )
  
  barcode_res = read_delim(input_file) %>%
    pivot_longer(-sp, names_to = 'marker', values_to = 'closest_reference_sample_id') %>%
    rename(sample_id = 'sp') %>%
    mutate(
      sample_id = str_remove_all(sample_id, '@.+'),
      closest_reference_sample_id = str_remove_all(closest_reference_sample_id, '@.+'),
      predicted_labels = samp_labels$actual_labels[match(closest_reference_sample_id, samp_labels$sample_id)],
      actual_labels = samp_labels$actual_labels[match(sample_id, samp_labels$sample_id)]
    ) %>%
    filter(marker != 'Concatenated_phylogeny') %>%
    mutate(
      query_labels = str_remove(actual_labels, ";*low_quality:True;*") %>% str_split(';'),
      predicted_list = str_split(predicted_labels, ';')
    ) %>%
    rowwise() %>%
    mutate(
      family_correct = query_labels[str_detect(query_labels, 'family')] %in% predicted_list,
      genus_correct = query_labels[str_detect(query_labels, 'genus')] %in% predicted_list,
      species_correct = ifelse(any(str_detect(
        query_labels, 'species'
      )),
      query_labels[str_detect(query_labels, 'species')] %in% predicted_list,
      NA),
      family_incorrect = any(!(predicted_list[str_detect(predicted_list, 'family')] %in% query_labels[str_detect(query_labels, 'family')])),
      genus_incorrect = any(!(predicted_list[str_detect(predicted_list, 'genus')] %in% query_labels[str_detect(query_labels, 'genus')])),
      species_incorrect = ifelse(any(str_detect(
        query_labels, 'species'
      )),
      any(!(
        predicted_list[str_detect(predicted_list, 'species')] %in% query_labels[str_detect(query_labels, 'species')]
      )),
      NA)
    ) %>%
    mutate_at(vars(ends_with("_correct"), ends_with("_incorrect")),
              ~ ifelse(is.na(predicted_labels) & !is.na(.), FALSE, .)) %>%
    mutate(query_bp = bp * 1e6)
  
  return(barcode_res)
}

Now we can apply this function to all of our results:

results_barcodes = purrr::map_dfr(c(0.5,1,2,5,10,20,50,100,200),read_traditional_barcodes)
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 285 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 267 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 200 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 166 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
results_barcodes

Now let’s summarise for each marker separately:

barcode_summary_family = split(results_barcodes,results_barcodes$marker) %>%
  purrr::map_dfr(~summarize_results(.x,'family'),.id='marker')

barcode_summary_family
barcode_summary_genus = split(results_barcodes,results_barcodes$marker) %>%
  purrr::map_dfr(~summarize_results(.x,'genus'),.id='marker')

barcode_summary_genus
barcode_summary_species = split(results_barcodes,results_barcodes$marker) %>%
  purrr::map_dfr(~summarize_results(.x,'species'),.id='marker')

barcode_summary_species

Now let’s plot, making separate plots for each marker:

Species:

p_barcode_species = barcode_summary_species %>%
  split(barcode_summary_species$marker) %>%
  purrr::map(~plot_area(.x,paste0(unique(.x$marker),' species'), relative = TRUE, xlim_all = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_species
$ITS

$matK

$ndhF

$rbcL

$`trnL-F`

Genera:

p_barcode_genus = barcode_summary_genus %>%
  split(barcode_summary_genus$marker) %>%
  purrr::map(~plot_area(.x,paste0(unique(.x$marker),' genus'), relative = TRUE, xlim_all = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_genus
$ITS

$matK

$ndhF

$rbcL

$`trnL-F`

Family:

p_barcode_family = barcode_summary_family %>%
  split(barcode_summary_family$marker) %>%
  purrr::map(~plot_area(.x,paste0(unique(.x$marker),' family'), relative = TRUE,xlim_all = TRUE))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_barcode_family
$ITS

$matK

$ndhF

$rbcL

$`trnL-F`

Concatenated tree

Now we will do the same for concatenated tree. Let’s start by defining a function to gather results. We will consider a result as correct if the majority of the sister taxon to a tip has the same label.


read_concatenated_tree_results = function(bp){
  
  
# Read in your tree - replace 'your_tree_file.nwk' with the path to your tree file
tree = read.tree(paste0('Malpighiales/traditional_barcodes/2_blast_phylogeny_result/Genus/conc.',bp,'m.spname.tre'))

#leave only sample IDs as tip labels
tree$tip.label = tree$tip.label %>% str_remove(".*@") %>% str_remove("'") %>% str_replace(' ref','_ref')

# Compute the patristic distances and list all reference names
patristic_distances <- cophenetic(tree)
all_ref_names = dimnames(patristic_distances)[[1]][str_detect(dimnames(patristic_distances)[[1]],'_ref$')]
all_nonref = dimnames(patristic_distances)[[1]][str_detect(dimnames(patristic_distances)[[1]],'_ref$',negate = TRUE)]

# For each tip, find the reference sample with closest patristic distance
find_closest = function(tip){
  to_keep = c(tip,all_ref_names[str_detect(all_ref_names,paste0(tip,'_ref'),negate = TRUE)])
  return(names(sort(patristic_distances[tip,to_keep])[2]) %>%
           str_remove('_ref'))
}

closest_match = purrr::map_chr(all_nonref,find_closest)

samples_with_data = read_delim(paste0('Malpighiales/traditional_barcodes/2_blast_phylogeny_result/Genus/',bp,'M_blast_phylo_sum_sp.tsv')) %>% 
  select(sample_id=sp) %>%
  mutate(sample_id = str_remove_all(sample_id, '@.+'))

barcode_res = tibble(sample_id = all_nonref,
       closest_reference_sample_id = closest_match) %>%
  right_join(samples_with_data) %>%
  mutate(
      predicted_labels = samp_labels$actual_labels[match(closest_reference_sample_id, samp_labels$sample_id)],
      actual_labels = samp_labels$actual_labels[match(sample_id, samp_labels$sample_id)]
    ) %>%
  filter(sample_id!='2095') %>%
  mutate(
      query_labels = str_remove(actual_labels, ";*low_quality:True;*") %>% str_split(';'),
      predicted_list = str_split(predicted_labels, ';')
    ) %>%
    rowwise() %>%
    mutate(
      family_correct = query_labels[str_detect(query_labels, 'family')] %in% predicted_list,
      genus_correct = query_labels[str_detect(query_labels, 'genus')] %in% predicted_list,
      species_correct = ifelse(any(str_detect(
        query_labels, 'species'
      )),
      query_labels[str_detect(query_labels, 'species')] %in% predicted_list,
      NA),
      family_incorrect = any(!(predicted_list[str_detect(predicted_list, 'family')] %in% query_labels[str_detect(query_labels, 'family')])),
      genus_incorrect = any(!(predicted_list[str_detect(predicted_list, 'genus')] %in% query_labels[str_detect(query_labels, 'genus')])),
      species_incorrect = ifelse(any(str_detect(
        query_labels, 'species'
      )),
      any(!(
        predicted_list[str_detect(predicted_list, 'species')] %in% query_labels[str_detect(query_labels, 'species')]
      )),
      NA)
    ) %>%
    mutate_at(vars(ends_with("_correct"), ends_with("_incorrect")),
              ~ ifelse(is.na(predicted_labels) & !is.na(.), FALSE, .)) %>%
    mutate(query_bp = bp * 1e6)
  
  return(barcode_res)
}

Now let’s apply this function

results_concat_barcodes = purrr::map_dfr(c(0.5,1,2,5,10,20,50,100,200),read_concatenated_tree_results)
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): sp, matK, rbcL, ndhF, trnL-F, ITS
lgl (1): Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 288 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 285 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 267 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 200 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Rows: 166 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: "\t"
chr (7): sp, matK, rbcL, ndhF, trnL-F, ITS, Concatenated_phylogeny
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`
results_concat_barcodes

Let’s summarize results and plot for genus, species and family accuracy

concat_summary_species = summarize_results(results_concat_barcodes,'species')
p_concat_species = plot_area(concat_summary_species, relative = FALSE,title = 'Concatenated barcodes species',xlim_all = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_concat_species

concat_summary_genus = summarize_results(results_concat_barcodes,'genus')
p_concat_genus = plot_area(concat_summary_genus, relative = TRUE,title = 'Concatenated barcodes genus',xlim_all = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_concat_genus

concat_summary_family = summarize_results(results_concat_barcodes,'family')
p_concat_family = plot_area(concat_summary_family, relative = TRUE,title = 'Concatenated barcodes family',xlim_all = TRUE)
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_concat_family

Direct comparison

Now let’s compare methods side by side. For genus level:

p = patchwork::wrap_plots(p_genus + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()), 
                   p_skmer_genus + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_genus$ITS + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_genus$rbcL + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_concat_genus,
                   ncol = 1) +
  plot_annotation(title = 'Genus-level accuracy')
p

safe_ggsave ('images_manuscript/fig3_genus_accuracy.pdf', width=5,height = 10)
Warning: File images_manuscript/fig3_genus_accuracy.pdf already exists. File not overwritten.
safe_ggsave ('images_manuscript/fig3_genus_accuracy.png', width=5,height = 10,dpi=1200)
Warning: File images_manuscript/fig3_genus_accuracy.png already exists. File not overwritten.

Now for species level:

p = patchwork::wrap_plots(p_species + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()), 
                   p_skmer_species + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_species$ITS + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_species$rbcL + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_concat_species,
                   ncol = 1) +
  plot_annotation(title = 'species-level accuracy')
p

safe_ggsave ('images_manuscript/fig3_species_accuracy.pdf', width=5,height = 10)
Warning: File images_manuscript/fig3_species_accuracy.pdf already exists. File not overwritten.
safe_ggsave ('images_manuscript/fig3_species_accuracy.png', width=5,height = 10,dpi=1200)
Warning: File images_manuscript/fig3_species_accuracy.png already exists. File not overwritten.

Now for family level:

p = patchwork::wrap_plots(p_family + ggtitle('varKoder') + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank(),
                                           legend.position = 'none'), 
                   p_skmer_family + ggtitle('Skmer') + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank(),
                                          legend.position = 'none'),
                   p_barcode_family$ITS + ggtitle('ITS') + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank()),
                   p_barcode_family$rbcL + ggtitle('rbcL') + theme(axis.text.x = element_blank(),
                                           axis.title.x = element_blank(),
                                           legend.position = 'none'),
                   p_concat_family + ggtitle('Concatenated conventional barcodes') + theme(legend.position = 'none'),
                   ncol = 1,guides = 'collect') +
  plot_annotation(title = 'Family-level accuracy',theme = theme(plot.title=element_text(hjust=0.5)))
p

safe_ggsave ('images_manuscript/fig3_family_accuracy.pdf', width=5,height = 10)
Warning: File images_manuscript/fig3_family_accuracy.pdf already exists. File not overwritten.
safe_ggsave ('images_manuscript/fig3_family_accuracy.png', width=5,height = 10,dpi=1200)
Warning: File images_manuscript/fig3_family_accuracy.png already exists. File not overwritten.

Now let’s plot a figure for the other traditional barcode loci that did not make this list.

p1 = patchwork::wrap_plots(ggplot() + 
                             theme_minimal() + 
                             ggtitle("Species") + 
                             theme(plot.title = element_text(hjust=0.5),
                                   plot.margin = unit(c(0,0,0,0),'lines')),
                           p_barcode_species$matK + 
                             ggtitle('matK') +
                              theme(axis.text.x = element_blank(),
                                    axis.title.x = element_blank(),
                                    axis.title.y =element_blank(),
                                    legend.position = 'none'),
                           p_barcode_species$ndhF +
                             ggtitle('ndhF') +
                              theme(axis.text.x = element_blank(),
                                    axis.title.x = element_blank(),
                                    axis.title.y =element_blank(),
                                    legend.position = 'none'),
                           p_barcode_species$`trnL-F` +
                             ggtitle('trnL-F') +
                             theme(axis.title.y =element_blank(),
                                   legend.position = 'none',
                                   axis.title.x =element_blank()),
                   ncol = 1,
                  heights = c(1,15,15,15)
                  ) 


p2 = patchwork::wrap_plots(ggplot() + 
                             theme_minimal() + 
                             ggtitle("Genus") + 
                             theme(plot.title = element_text(hjust=0.5),
                                   plot.margin = unit(c(0,0,0,0),'lines')),
                           p_barcode_genus$matK + 
                             ggtitle('matK') +
                              theme(axis.text.x = element_blank(),
                                    axis.title.x = element_blank(),
                                    axis.title.y =element_blank(),
                                    axis.text.y =element_blank(),
                                    legend.position = 'none'),
                           p_barcode_genus$ndhF +
                             ggtitle('ndhF') +
                              theme(axis.text.x = element_blank(),
                                    axis.title.x = element_blank(),
                                    axis.title.y =element_blank(),
                                    axis.text.y =element_blank(),
                                    legend.position = 'none'),
                           p_barcode_genus$`trnL-F` +
                             ggtitle('trnL-F') +
                             theme(axis.title.y =element_blank(),
                                    axis.text.y =element_blank(),
                                   legend.position = 'none',
                                   axis.title.x =element_blank()),
                   ncol = 1,
                  heights = c(1,15,15,15)) 

p3 = patchwork::wrap_plots(ggplot() + 
                             theme_minimal() + 
                             ggtitle("Family") + 
                             theme(plot.title = element_text(hjust=0.5),
                                   plot.margin = unit(c(0,0,0,0),'lines')),
                           p_barcode_family$matK + 
                             ggtitle('matK') +
                              theme(axis.text.x = element_blank(),
                                    axis.title.x = element_blank(),
                                    axis.title.y =element_blank(),
                                    axis.text.y =element_blank(),
                                    legend.position = 'none'),
                           p_barcode_family$ndhF +
                             ggtitle('ndhF') +
                              theme(axis.text.x = element_blank(),
                                    axis.title.x = element_blank(),
                                    axis.title.y =element_blank(),
                                    axis.text.y =element_blank(),
                                    legend.position = 'none'),
                           p_barcode_family$`trnL-F` +
                             ggtitle('trnL-F') +
                             theme(axis.title.y =element_blank(),
                                    axis.text.y =element_blank(),
                                   axis.title.x =element_blank()),
                   ncol = 1,
                   heights = c(1,15,15,15),
                   guides='collect')

p = patchwork::wrap_plots(p1,p2,p3,ncol=3,guides="collect") +
   plot_annotation(
    title = 'Conventional barcode accuracy across different taxonomic levels',
    theme = theme(plot.title = element_text(hjust = 0.2,face='bold',size=15))
  )

safe_ggsave ('images_manuscript/supp_traditional_barcodes.pdf', plot = p, width=8,height = 6)
Warning: File images_manuscript/supp_traditional_barcodes.pdf already exists. File not overwritten.

SRA: eukaryotic families

varKodes

Finally, let’s summarize results for the whole SRA dataset. In this case, we only have varKoder since Skmer cannot finish and traditional barcodes are inapplicable. We have done predictions separately for families included in the training set and families not included in the trainings set, so we will load each one and concatenate

varKoder_SRA_results  = read_csv('all_SRA_eukaryote_families/vkfCGR_query_results/predictions.csv') %>%
select(-1) %>%
  mutate(query_basepairs = case_when(
    is.character(query_basepairs) ~ as.numeric(str_remove(query_basepairs, "K")) * 1000,
    TRUE ~ as.numeric(query_basepairs)
  )) %>%
  filter(str_detect(format(query_basepairs, scientific = FALSE,trim = TRUE), "^[125]0+$")) %>% #we will ignore queries that are not standardized sizes
  rename(query_bp = query_basepairs) %>%
  mutate(quality_included = T)
Rows: 8607 Columns: 12── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (6): varKode_image_path, sample_id, query_mapping, trained_model_path, pr...
dbl (5): query_basepairs, query_kmer_len, actual_labels, basefrequency_sd, pr...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
plan(sequential)


varKoder_SRA_results = varKoder_SRA_results %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist,
         predicted_list = str_split(predicted_labels,';')
         ) %>%
  rowwise() %>%
  mutate(family_correct = query_labels %in% predicted_list,
         family_incorrect = ifelse(is.na(predicted_labels),FALSE,any(!(predicted_list %in% query_labels))),
         family_in_training = TRUE) %>%
 select(matches("^[^0-9]"))

varKoder_SRA_results 
NA
varKoder_SRA_results_notincluded  = read_csv('all_SRA_eukaryote_families/vkfCGR_query_notincluded_results/predictions.csv') %>%
select(-1) %>%
  mutate(query_basepairs = case_when(
    is.character(query_basepairs) ~ as.numeric(str_remove(query_basepairs, "K")) * 1000,
    TRUE ~ as.numeric(query_basepairs)
  )) %>%
  filter(str_detect(format(query_basepairs, scientific = FALSE,trim = TRUE), "^[125]0+$")) %>% #we will ignore queries that are not standardized sizes
  rename(query_bp = query_basepairs) %>%
  mutate(quality_included = T)
Rows: 8439 Columns: 12── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (6): varKode_image_path, sample_id, query_mapping, trained_model_path, pr...
dbl (5): query_basepairs, query_kmer_len, actual_labels, basefrequency_sd, pr...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
plan(sequential)

SRA_taxlabels_notincluded = str_remove(varKoder_SRA_results_notincluded$actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist %>% unique

varKoder_SRA_results_notincluded = varKoder_SRA_results_notincluded %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist,
         predicted_list = str_split(predicted_labels,';')
         ) %>%
  rowwise() %>%
  mutate(family_correct = query_labels %in% predicted_list,
         family_incorrect = ifelse(is.na(predicted_labels),FALSE,any(!(predicted_list %in% query_labels))),
         family_in_training = FALSE) %>%
 select(matches("^[^0-9]"))

varKoder_SRA_results_notincluded 

Now let’s summarize and plot:

SRA_summary_family = bind_rows(summarize_results(varKoder_SRA_results,'family') %>% mutate(family_in_training = TRUE),
                               summarize_results(varKoder_SRA_results_notincluded,'family') %>% mutate(family_in_training = FALSE))
SRA_summary_family

N_samp = SRA_summary_family %>%
 group_by(query_bp, family_in_training) %>%
 summarise(N = sum(N))
`summarise()` has grouped output by 'query_bp'. You can override using the `.groups` argument.
p_SRA_family = plot_area(SRA_summary_family, 'varKoder SRA family', relative = TRUE,xlim_all = FALSE, wrap = '~family_in_training')
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p_SRA_family 

Let’s now do the SRA plot, but splitting by kingdom and whether or not family was included in training. First, we need to retrieve kingdom information:


summary_SRA_by_kingdom = read_csv('all_SRA_eukaryote_families/runs_to_download_data.csv') %>%
  select(sample_id = Run, Kingdom) %>%
  right_join(varKoder_SRA_results) %>%
  split(.$Kingdom) %>%
  purrr::map_df(summarize_results, 
                 level='family',
                .id='Kingdom'
                ) %>%
  mutate(Kingdom = factor(Kingdom,levels=c('Metazoa','Viridiplantae','Fungi'),ordered = T),
         family_in_training = T) %>%
  bind_rows(read_csv('all_SRA_eukaryote_families/runs_notincluded_to_download_data.csv') %>%
  select(sample_id = Run, Kingdom) %>%
  right_join(varKoder_SRA_results_notincluded) %>%
  split(.$Kingdom) %>%
  purrr::map_df(summarize_results, 
                 level='family',
                .id='Kingdom'
                ) %>%
  mutate(Kingdom = factor(Kingdom,levels=c('Metazoa','Viridiplantae','Fungi'),ordered = T),
         family_in_training = F)) %>%
  mutate(family_in_training = c('Family\nnot in training set', 'Family\nin training set')[family_in_training+1])
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 8264 Columns: 51── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr  (28): Run, AssemblyName, download_path, Experiment, LibraryName, Library...
dbl  (11): spots, bases, spots_with_mates, avgLength, size_MB, InsertSize, In...
lgl  (10): g1k_pop_code, source, g1k_analysis_group, Subject_ID, Disease, Aff...
dttm  (2): ReleaseDate, LoadDate
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 1697 Columns: 51── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr  (27): Run, download_path, Experiment, LibraryName, LibraryStrategy, Libr...
dbl  (11): spots, bases, spots_with_mates, avgLength, size_MB, InsertSize, In...
lgl  (11): AssemblyName, g1k_pop_code, source, g1k_analysis_group, Subject_ID...
dttm  (2): ReleaseDate, LoadDate
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`
summary_SRA_by_kingdom 


p_SRA_families = plot_area(summary_SRA_by_kingdom ,
          relative=FALSE,
          xlim_all = FALSE,
          title='Eukaryote families') + 
  facet_grid(family_in_training~Kingdom) +
  coord_cartesian(xlim=c(500,10000)*1000,expand = FALSE) +
  theme(text = element_text(size=10))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Coordinate system already present. Adding new coordinate system, which will
replace the existing one.
print(p_SRA_families)



safe_ggsave ('images_manuscript/fig3_SRA_accuracy.pdf', width=5,height = 4)
Warning: File images_manuscript/fig3_SRA_accuracy.pdf already exists. File not overwritten.
safe_ggsave ('images_manuscript/fig3_SRA_accuracy.png', width=5,height = 4,dpi = 1200)
Warning: File images_manuscript/fig3_SRA_accuracy.png already exists. File not overwritten.

VarKodes

We repeated this analysis with varKodes. Let’s compare now.

plan(sequential)

varKoder_SRA_results_vk = read_csv('all_SRA_eukaryote_families/varkoder_query_results/predictions.csv',
                                    col_types = list(query_basepairs = "c")) %>%
  select(-1) %>%
  filter(str_detect(query_basepairs,'^0*[125]0+K$')) %>%
  mutate(query_bp = as.numeric(str_remove(query_basepairs,'K'))*1000)
New names:
varKoder_SRA_results_vk = varKoder_SRA_results_vk %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist,
         predicted_list = str_split(predicted_labels,';')
  ) %>%
  rowwise() %>%
  mutate(family_correct = query_labels %in% predicted_list,
         family_incorrect = ifelse(is.na(predicted_labels),FALSE,any(!(predicted_list %in% query_labels))),
         family_in_training = TRUE) %>%
  select(matches("^[^0-9]")) 
  

varKoder_SRA_results_notincluded_vk = read_csv('all_SRA_eukaryote_families/varkoder_query_notincluded_results/predictions.csv',
                                                col_types = list(query_basepairs = "c")) %>%
  select(-1) %>%
  filter(str_detect(query_basepairs,'^0*[125]0+K$')) %>%
  mutate(query_bp = as.numeric(str_remove(query_basepairs,'K'))*1000)
New names:
SRA_taxlabels_notincluded_vk = str_remove(varKoder_SRA_results_vk$actual_labels,";*low_quality:True;*") %>% 
  str_split(';') %>% 
  unlist %>% 
  unique 

varKoder_SRA_results_notincluded_vk = varKoder_SRA_results_notincluded_vk %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist,
         predicted_list = str_split(predicted_labels,';')
  ) %>%
  rowwise() %>%
  mutate(family_correct = query_labels %in% predicted_list,
         family_incorrect = ifelse(is.na(predicted_labels),FALSE,any(!(predicted_list %in% query_labels))),
         family_in_training = FALSE) %>%
  select(matches("^[^0-9]"))

# Summary statistics
SRA_summary_family_vk = bind_rows(
  summarize_results(varKoder_SRA_results_vk,'family') %>% mutate(family_in_training = TRUE),
  summarize_results(varKoder_SRA_results_notincluded_vk,'family') %>% mutate(family_in_training = FALSE)
)

N_samp_vk = SRA_summary_family_vk %>%
  group_by(query_bp, family_in_training) %>%
  summarise(N = sum(N))
`summarise()` has grouped output by 'query_bp'. You can override using the `.groups` argument.
# Kingdom summary
summary_SRA_by_kingdom_vk = read_csv('all_SRA_eukaryote_families/runs_to_download_data.csv') %>%
  select(sample_id = Run, Kingdom) %>%
  right_join(varKoder_SRA_results_vk) %>%
  split(.$Kingdom) %>%
  purrr::map_df(summarize_results, 
                level='family',
                .id='Kingdom') %>%
  mutate(Kingdom = factor(Kingdom,levels=c('Metazoa','Viridiplantae','Fungi'),ordered = TRUE),
         family_in_training = TRUE) %>%
  bind_rows(
    read_csv('all_SRA_eukaryote_families/runs_notincluded_to_download_data.csv') %>%
      select(sample_id = Run, Kingdom) %>%
      right_join(varKoder_SRA_results_notincluded_vk) %>%
      split(.$Kingdom) %>%
      purrr::map_df(summarize_results, 
                    level='family',
                    .id='Kingdom') %>%
      mutate(Kingdom = factor(Kingdom,levels=c('Metazoa','Viridiplantae','Fungi'),ordered = TRUE),
             family_in_training = FALSE)
  ) %>%
  mutate(family_in_training = c('Family\nnot in training set', 'Family\nin training set')[family_in_training+1])
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 8264 Columns: 51── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr  (28): Run, AssemblyName, download_path, Experiment, LibraryName, Library...
dbl  (11): spots, bases, spots_with_mates, avgLength, size_MB, InsertSize, In...
lgl  (10): g1k_pop_code, source, g1k_analysis_group, Subject_ID, Disease, Aff...
dttm  (2): ReleaseDate, LoadDate
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 1697 Columns: 51── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr  (27): Run, download_path, Experiment, LibraryName, LibraryStrategy, Libr...
dbl  (11): spots, bases, spots_with_mates, avgLength, size_MB, InsertSize, In...
lgl  (11): AssemblyName, g1k_pop_code, source, g1k_analysis_group, Subject_ID...
dttm  (2): ReleaseDate, LoadDate
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)`
# Final plot
p_SRA_families_vk = plot_area(summary_SRA_by_kingdom_vk,
                          relative=FALSE,
                          xlim_all = FALSE,
                          title='Eukaryote families') + 
  facet_grid(family_in_training~Kingdom) +
  coord_cartesian(xlim=c(500,10000)*1000,expand = FALSE) +
  theme(text = element_text(size=10))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.Coordinate system already present. Adding new coordinate system, which will
replace the existing one.
p_SRA_families_vk

The graph looks almost identical, let’s make a table comparing side-by-side. It seems rfCGRs are slightly better.

full_join(
select(summary_SRA_by_kingdom_vk,Kingdom,query_bp,result,p,family_in_training),
select(summary_SRA_by_kingdom,,Kingdom,query_bp,result,p,family_in_training),
by=join_by(Kingdom,query_bp,result,family_in_training),
suffix = c('.varKode','.rfCGR')
) %>%
  mutate(difference = p.varKode-`p.rfCGR`) %>%
  arrange(family_in_training,result,query_bp,Kingdom)

Other species-level datasets

varKodes

Now we will make a small figure to include the additional datasets in which we applied varKoding.

In these cases, we chose a test set that included both taxa in the training set and taxa not in the training set, so we will graph both separately. This is denoted by a column named in_training_model. Let’s start by reading results.

Let’s define a function to read and process predictions:

read_and_process_others = function(infile){
  
varkoder_results = read_csv(infile) %>% 
  mutate(sample_id = as.character(sample_id)) %>%
  select(-1) %>%
  rename(query_bp = query_basepairs) 


all_taxlabels = str_remove(varkoder_results$actual_labels,";*low_quality:True;*") %>% str_split(';') %>% unlist %>% unique

varkoder_results = varkoder_results %>%
  mutate(query_labels = str_remove(actual_labels,";*low_quality:True;*") %>% str_split(';'),
         predicted_list = str_split(predicted_labels,';')
         ) %>%
  rowwise() %>%
  mutate(taxon_correct = any(query_labels %in% predicted_list),
         taxon_incorrect = any(!(predicted_list[!is.na(predicted_list)] %in% query_labels))
         )

return(varkoder_results)
}

Now let’s apply this function to all files.

prediction_files = list.files('other_datasets/varKode/',pattern = 'predictions.+csv',full.names = T,recursive = T)
names(prediction_files) = basename(prediction_files) %>% str_extract(".*(?=_predictions\\.csv)")

other_results = purrr::map_dfr(prediction_files, read_and_process_others, .id='dataset')
Rows: 18 Columns: 16── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (7): sample_id, query_basepairs, query_kmer_len, prediction_type, in_trai...
dbl (8): Bembidion, a, basefrequency_sd, ampliatum, breve, lividulum, saturat...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 18 Columns: 16── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (7): sample_id, query_basepairs, query_kmer_len, prediction_type, in_trai...
dbl (8): Corallorhiza, prediction_threshold, basefrequency_sd, Corallorhiza b...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 25 Columns: 16── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (7): sample_id, query_basepairs, query_kmer_len, prediction_type, in_trai...
dbl (8): Mycobacterium tuberculosis, prediction_threshold, basefrequency_sd, ...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 15 Columns: 16── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (7): sample_id, query_basepairs, query_kmer_len, prediction_type, in_trai...
dbl (8): Xanthoparmelia, prediction_threshold, basefrequency_sd, camtschadali...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
other_results

Let’s now summarize by dataset and separately for taxa included and excluded from the training set.

summary_others = other_results %>%
  split(interaction(other_results$dataset, other_results$in_training_model)) %>%
  purrr::map_dfr(summarize_results, level = 'taxon', .id = 'comb') %>%
  separate(comb, into = c("dataset", "taxon_in_training_raw"), sep = "\\.") %>%
  mutate(taxon_in_training = taxon_in_training_raw == 'yes') %>%
  select(-taxon_in_training_raw) %>%
  mutate(taxon_in_training = c('Taxon not in training set', 'Taxon in training set')[taxon_in_training+1],
         dataset = str_replace(dataset, "^(.)", ~toupper(.x))) %>%
  mutate(result = factor(result,
                         levels=c("correct", "ambiguous", "inconclusive", "incorrect"),
                         ordered=T))
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `query_bp = as.numeric(as.character(query_bp))`.
Caused by warning:
! NAs introduced by coercionWarning: There was 1 warning in `mutate()`.
ℹ In argument: `query_bp = as.numeric(as.character(query_bp))`.
Caused by warning:
! NAs introduced by coercionWarning: There was 1 warning in `mutate()`.
ℹ In argument: `query_bp = as.numeric(as.character(query_bp))`.
Caused by warning:
! NAs introduced by coercionWarning: There was 1 warning in `mutate()`.
ℹ In argument: `query_bp = as.numeric(as.character(query_bp))`.
Caused by warning:
! NAs introduced by coercionWarning: There was 1 warning in `mutate()`.
ℹ In argument: `query_bp = as.numeric(as.character(query_bp))`.
Caused by warning:
! NAs introduced by coercionWarning: There was 1 warning in `mutate()`.
ℹ In argument: `query_bp = as.numeric(as.character(query_bp))`.
Caused by warning:
! NAs introduced by coercionWarning: There was 1 warning in `mutate()`.
ℹ In argument: `query_bp = as.numeric(as.character(query_bp))`.
Caused by warning:
! NAs introduced by coercionWarning: There was 1 warning in `mutate()`.
ℹ In argument: `query_bp = as.numeric(as.character(query_bp))`.
Caused by warning:
! NAs introduced by coercion
summary_others
NA

Now let’s plot

p_others = ggplot(summary_others , aes(x = dataset, y = N, fill = result)) +
  geom_col()+
    scale_fill_manual(values = setNames(RColorBrewer::brewer.pal(4, "Accent"), c("correct", "ambiguous", "inconclusive", "incorrect"))) +
    scale_alpha_manual(values=c(0.5,1)) +
    ggtitle('Other datasets') +
    ylab('Number of samples') +
    xlab('Taxon') +
    theme_few() +
      scale_y_continuous(minor_breaks = waiver()) +
      theme(panel.background = element_rect(fill = NA),
            panel.grid.major.y = element_line(colour = gray(0.5)),
            panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
            axis.title.x = element_blank(),
            axis.text.x = element_text(face='italic'),
            panel.ontop = TRUE) +
    coord_cartesian(expand=FALSE) +
    facet_grid(taxon_in_training~.) +
    theme(text = element_text(size=10),
          axis.text.x = element_text(angle = 30,hjust = 1))
  
p_others

CGR

Let’s repeat everything for CGR representation now

# Read prediction files
prediction_files_CGR = list.files('other_datasets/cgr/',pattern = 'predictions.+csv',full.names = TRUE,recursive = T)
names(prediction_files_CGR) = basename(prediction_files_CGR) %>% str_extract(".*(?=_predictions\\.csv)")
other_results_CGR = purrr::map_dfr(prediction_files_CGR, read_and_process_others, .id='dataset')
Rows: 18 Columns: 18── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (8): varKode_image_path, sample_id, in_training_model, query_mapping, tra...
dbl (9): query_basepairs, query_kmer_len, basefrequency_sd, prediction_thresh...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 18 Columns: 18── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (8): varKode_image_path, sample_id, in_training_model, query_mapping, tra...
dbl (9): query_basepairs, query_kmer_len, basefrequency_sd, prediction_thresh...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 25 Columns: 18── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (8): varKode_image_path, sample_id, in_training_model, query_mapping, tra...
dbl (9): query_basepairs, query_kmer_len, basefrequency_sd, prediction_thresh...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 15 Columns: 18── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (8): varKode_image_path, sample_id, in_training_model, query_mapping, tra...
dbl (9): query_basepairs, query_kmer_len, basefrequency_sd, prediction_thresh...
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Create summary
summary_others_CGR = other_results_CGR %>%
  split(interaction(other_results_CGR$dataset, other_results_CGR$in_training_model)) %>%
  purrr::map_dfr(summarize_results, level = 'taxon', .id = 'comb') %>%
  separate(comb, into = c("dataset", "taxon_in_training_raw"), sep = "\\.") %>%
  mutate(taxon_in_training = taxon_in_training_raw == 'yes') %>%
  select(-taxon_in_training_raw) %>%
  mutate(taxon_in_training = c('Taxon not in training set', 'Taxon in training set')[taxon_in_training+1],
         dataset = str_replace(dataset, "^(.)", ~toupper(.x))) %>%
  mutate(result = factor(result,
                        levels=c("correct", "ambiguous", "inconclusive", "incorrect"),
                        ordered=TRUE))

# Create plot
p_others_CGR = ggplot(summary_others_CGR, aes(x = dataset, y = N, fill = result)) +
  geom_col() +
  scale_fill_manual(values = setNames(RColorBrewer::brewer.pal(4, "Accent"), 
                                    c("correct", "ambiguous", "inconclusive", "incorrect"))) +
  scale_alpha_manual(values=c(0.5,1)) +
  ggtitle('Other datasets') +
  ylab('Number of samples') +
  xlab('Taxon') +
  theme_few() +
  scale_y_continuous(minor_breaks = waiver()) +
  theme(panel.background = element_rect(fill = NA),
        panel.grid.major.y = element_line(colour = gray(0.5)),
        panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
        axis.title.x = element_blank(),
        axis.text.x = element_text(face='italic'),
        panel.ontop = TRUE) +
  coord_cartesian(expand=FALSE) +
  facet_grid(taxon_in_training~.) +
  theme(text = element_text(size=10),
        axis.text.x = element_text(angle = 30,hjust = 1))

p_others_CGR


safe_ggsave ('images_manuscript/fig3_others_accuracy.pdf', width=3.5,height = 3)
Warning: File images_manuscript/fig3_others_accuracy.pdf already exists. File not overwritten.
safe_ggsave ('images_manuscript/fig3_others_accuracy.png', width=3.5,height = 3,dpi = 1200)
Warning: File images_manuscript/fig3_others_accuracy.png already exists. File not overwritten.

SRA: all taxa

unpack_labels function

This function takes a string of semicolon-separated key-value pairs and converts it into a structured dataframe. It splits the input string on semicolons, then further splits each pair on colons to separate keys and values. The result is a two-column dataframe with ‘key’ and ‘value’ columns, providing an organized representation of the label data.

unpack_labels = function(x){
  # Split the input string by ';'
  kvs = strsplit(x, ';')[[1]]
  
  # Split each key-value pair by ':'
  kvs_split = strsplit(kvs, ':')
  
  # Create a dataframe with columns "key" and "value"
  df = data.frame(
    key = sapply(kvs_split, `[`, 1),
    value = sapply(kvs_split, `[`, 2),
    stringsAsFactors = FALSE
  )
  
  return(df)
}

summarize_comparison function

This function compares two sets of labels (actual and predicted) and generates a summary of their agreement. It processes all unique keys found in both sets, excluding certain taxonomy-related keys, and includes a special ‘Taxonomy_all’ key. For each key, it calculates the number of actual and predicted values, true positives (TP), false negatives (FN), and false positives (FP). The output is a dataframe summarizing these metrics for each key, enabling detailed analysis of prediction accuracy across different label types.

summarize_comparison <- function(actual, predicted) {
  
  # Ensure the key columns are of type character to avoid factor level issues
  actual$key <- as.character(actual$key)
  predicted$key <- as.character(predicted$key)
  
  # Get the unique keys from both actual and predicted, excluding specific keys
  all_keys <- unique(c(actual$key, predicted$key))
  filtered_keys <- setdiff(all_keys, c("Taxonomy_no_rank", "Taxonomy_clade"))
  
  # Add "Taxonomy_all" key to the list of keys
  if (!"Taxonomy_all" %in% filtered_keys) {
    filtered_keys <- c(filtered_keys, "Taxonomy_all")
  }
  
  # Initialize a result dataframe
  result <- data.frame(
    key = filtered_keys,
    N_actual = integer(length(filtered_keys)),
    N_predicted = integer(length(filtered_keys)),
    TP = integer(length(filtered_keys)),
    FN = integer(length(filtered_keys)),
    FP = integer(length(filtered_keys)),
    stringsAsFactors = FALSE
  )
  
  for (key in filtered_keys) {
    if (key == "Taxonomy_all") {
      actual_values <- actual$value[grepl("^Taxonomy_", actual$key)]
      predicted_values <- predicted$value[grepl("^Taxonomy_", predicted$key)]
    } else {
      actual_values <- actual$value[actual$key == key]
      predicted_values <- predicted$value[predicted$key == key]
    }
    
    N_actual <- length(actual_values)
    N_predicted <- length(predicted_values)
    TP <- sum(actual_values %in% predicted_values)
    FN <- sum(!actual_values %in% predicted_values)
    FP <- sum(!predicted_values %in% actual_values)
    
    result[result$key == key, ] <- list(key, N_actual, N_predicted, TP, FN, FP)
  }
  
  return(result)
}

process_dataframe_parallel function

This function performs parallel processing on a dataframe to extract and structure label information. It uses multiple cores to speed up processing of large datasets. For each row, it unpacks the actual and predicted labels, extracts library strategy and platform information, and structures this data into a new dataframe. The result is a processed dataframe with unpacked label information and additional metadata, optimized for further analysis.

process_dataframe_parallel <- function(df, num_cores = 16) {
  # Set up parallel processing
  plan(multisession, workers = num_cores)
  
  processed_df <- df %>%
    split(1:nrow(.)) %>%  # Split the dataframe into a list of rows
    future_map_dfr(~ {
      actual = unpack_labels(.x$actual_labels)
      predicted = unpack_labels(.x$predicted_labels)
      library_strategy = actual %>% 
        filter(key == 'LibraryStrategy') %>%
        pull(value)
      platform = actual %>% 
        filter(key == 'Platform') %>%
        pull(value)
      
      tibble(
        sample_id = .x$sample_id,
        query_basepairs = .x$query_basepairs,
        query_mapping = .x$query_mapping,
        actual = list(actual),
        predicted = list(predicted),
        library_strategy = library_strategy,
        platform = platform
      )
    }, .options = furrr_options(seed = TRUE))
  
  # Close the parallel backend
  plan(sequential)
  
  return(processed_df)
}

collate_results function

This function applies the summarize_comparison function to an entire dataframe of predictions. It processes each row, comparing actual and predicted labels and collecting metadata like sample ID, query base pairs, and mapping information. The function then combines all these individual summaries into a single, comprehensive dataframe. This resulting dataframe provides a detailed overview of prediction accuracy across all samples, including various metadata for each comparison.

collate_results <- function(predictions_df) {
  predictions_df = predictions_df %>%
    select(sample_id, query_basepairs, query_mapping, actual, predicted, platform, library_strategy)
  # Apply the summarize_comparison function to each row
  results_list <- pmap(predictions_df, function(sample_id, query_basepairs, query_mapping, actual, predicted, platform, library_strategy) {
    result <- summarize_comparison(actual, predicted)
    result$sample_id <- sample_id
    result$query_basepairs <- query_basepairs
    result$query_mapping <- query_mapping
    result$platform = platform
    result$library_strategy = library_strategy
    return(result)
  })
  
  # Combine all results into a single dataframe
  results_df <- bind_rows(results_list)
  return(results_df)
}

Read data

vk_df = read_csv('all_SRA_taxa/results_varKodes/predictions.csv') %>% process_dataframe_parallel
Rows: 152015 Columns: 12── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (7): varKode_image_path, sample_id, query_mapping, trained_model_path, ac...
dbl (4): query_basepairs, query_kmer_len, basefrequency_sd, prediction_threshold
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
cgr_df =  read_csv('all_SRA_taxa/results_cgrs/predictions.csv') %>% process_dataframe_parallel
Rows: 152015 Columns: 12── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (7): varKode_image_path, sample_id, query_mapping, trained_model_path, ac...
dbl (4): query_basepairs, query_kmer_len, basefrequency_sd, prediction_threshold
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
cgr_df
vk_df
vk_cgr_df = bind_rows(vk_df, cgr_df)
summaries = collate_results(vk_cgr_df) 
summaries

Now let’s calculate metrics:

summaries_with_scores <- summaries %>%
  group_by(key, query_basepairs, query_mapping, platform, library_strategy) %>%
  summarize(
    total_TP = sum(TP),
    total_FN = sum(FN),
    total_FP = sum(FP),
    N = n(),
    .groups = 'drop'
  ) %>%
  mutate(
    microprecision = total_TP / (total_TP + total_FP),
    microrecall = total_TP / (total_TP + total_FN),
    F1 = 2 * (microprecision * microrecall) / (microprecision + microrecall)
  ) %>%
  replace_na(list(microprecision = 0, microrecall = 0, F1 = 0)) %>%
  select(query_basepairs, query_mapping, key, F1, microprecision, microrecall, everything()) %>%
  filter(
      grepl("^[125]0{1,}$", format(query_basepairs, scientific = FALSE, trim = TRUE))
  )

summaries_with_scores

Plots

plot_df = summaries_with_scores %>%
  rename(
    precision = microprecision,
    recall = microrecall
  ) %>%
  pivot_longer(
    cols = c(F1, precision, recall),
    names_to = "metric",
    values_to = "metric_value"
  ) %>%
  filter(key %in% c("LibraryStrategy", "Platform", "Taxonomy_all", "Taxonomy_family", "Taxonomy_genus", "Taxonomy_species")) %>%
  mutate(query_mapping = ifelse(query_mapping=='cgr','rfCGR',query_mapping))

This graph shows how well the two methods behave to recognize the library strategy:

brks = c(500000,
             1000000,
             2000000,
             5000000,
             10000000,
             20000000,
             50000000,
             100000000,
             200000000
             )


plot_df_strat = plot_df %>% filter(key %in% c("LibraryStrategy"))

Ns <- plot_df_strat %>%
  group_by(platform, library_strategy) %>%
  summarize(N = max(N), .groups = "drop") %>%
  mutate(N = paste0("N = ", N))

ggplot(plot_df_strat,
       aes(x=query_basepairs, y=metric_value, color=metric,linetype=query_mapping,group=interaction(metric,query_mapping))) +
  geom_line() +
  facet_grid(platform~library_strategy) +
  labs(title="Accuracy in predicting the library strategy (GBS, RAD or WGS)",
       x="Metric value", 
       y="Base pairs in query images",
       color="Metric",
       linetype="K-mer Mapping") +
  scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = brks)  +
  scale_y_continuous(limits=c(0,1), breaks=c(0,0.2,0.4,0.6,0.8,1)) +
  theme_minimal() +
  theme(text = element_text(size=10),
        axis.text.x = element_text(hjust=1,angle=45)) +
   geom_label(data = Ns, aes(label = N, x = 12000000, y = 0.1),
             hjust = 0.5, vjust = 0.5,
             label.padding = unit(0.2, "lines"),
             label.size = 0.2,
             size=2,
             color = "black",
             fill = "white",
             inherit.aes = FALSE)


safe_ggsave ('images_manuscript/supp_strat_prediction.pdf',width=7,height = 7,useDingbats=F)
Warning: File images_manuscript/supp_strat_prediction.pdf already exists. File not overwritten.

This graph shows how well the two methods behave to recognize the sequencing platform:

ggplot(plot_df %>% filter(key %in% c("Platform")),
       aes(x=query_basepairs, y=metric_value, color=metric,linetype=query_mapping,group=interaction(metric,query_mapping,key))) +
  geom_line() +
  facet_grid(platform~library_strategy) +
  scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = brks)  +
  scale_y_continuous(limits=c(0,1), breaks=c(0,0.2,0.4,0.6,0.8,1)) +
  labs(title="Accuracy in predicting sequencing platform",
       x="Metric value", 
       y="Base pairs in query images",
       color="Metric",
       linetype="K-mer Mapping") +
  theme_minimal() +
  theme(text = element_text(size=10),
        axis.text.x = element_text(hjust=1,angle=45)) +
   geom_label(data = Ns, aes(label = N, x = 12000000, y = 0.1),
             hjust = 0.5, vjust = 0.5,
             label.padding = unit(0.2, "lines"),
             label.size = 0.2,
             size=2,
             color = "black",
             fill = "white",
             inherit.aes = FALSE)


safe_ggsave ('images_manuscript/supp_platform_prediction.pdf',width=7,height = 7,useDingbats=F)
Warning: File images_manuscript/supp_platform_prediction.pdf already exists. File not overwritten.

This graph shows how well the two methods behave for different taxonomic levels:

tax_labels = c("all ranks", "family", "genus", "species")
names(tax_labels) = c("Taxonomy_all", "Taxonomy_family", "Taxonomy_genus", "Taxonomy_species")

plot_df_tax = plot_df %>% 
  filter(key %in% c("Taxonomy_all", "Taxonomy_family", "Taxonomy_genus", "Taxonomy_species")) %>%
  mutate(key = tax_labels[key],
         platform = ifelse(platform=='OXFORD_NANOPORE','NANOPORE',platform))

Ns <- plot_df_tax  %>%
  group_by(platform, library_strategy,key) %>%
  summarize(N = max(N), .groups = "drop") %>%
  mutate(N = paste0("N = ", N))

ggplot(plot_df_tax,
       aes(x=query_basepairs, 
           y=metric_value, color=metric,
           linetype=query_mapping,
           group=interaction(metric,query_mapping,key))) +
  geom_line() +
  facet_grid(key~platform+library_strategy) +   
  scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = brks)  +
  scale_y_continuous(limits=c(0,1), breaks=c(0,0.2,0.4,0.6,0.8,1)) +
  geom_label(data = Ns, aes(label = N, x = 7000000, y = 0.1),
             hjust = 0.5, vjust = 0.5,
             label.padding = unit(0.2, "lines"),
             label.size = 0.2,
             size=2,
             color = "gray30" ,
             fill = "gray96",
             inherit.aes = FALSE) +
  labs(title="Accuracy in predicting taxonomy",
       y="Metric value", 
       x="Base pairs in query images",
       color="Metric",
       linetype="K-mer Mapping") +
  theme_minimal() +
  theme(text = element_text(size=7),
        axis.text.x = element_text(hjust=1,angle=45),
        legend.position = 'bottom')

For the manuscript, let’s simplify and only show rfCGR:

ggplot(plot_df_tax %>% filter(query_mapping=='rfCGR',metric != "F1"),
       aes(x=query_basepairs, 
           y=metric_value, color=metric,
           group=metric)) +
  geom_line() +
  facet_grid(key~platform+library_strategy) +   
  scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),breaks = brks)  +
  scale_y_continuous(limits=c(0,1), breaks=c(0,0.2,0.4,0.6,0.8,1)) +
  geom_label(data = Ns, aes(label = N, x = 7000000, y = 0.1),
             hjust = 0.5, vjust = 0.5,
             label.padding = unit(0.2, "lines"),
             label.size = 0.2,
             size=2,
             color = "gray30" ,
             fill = "gray96",
             inherit.aes = FALSE) +
  labs(title="Accuracy in predicting taxonomy",
       y="Metric value", 
       x="Base pairs in query images",
       color="Metric",
       linetype="K-mer Mapping") +
  theme_minimal() +
  theme(text = element_text(size=7),
        axis.text.x = element_text(hjust=1,angle=45),
        legend.position = 'bottom')


safe_ggsave ('images_manuscript/fig_X_taxonomy_prediction.pdf',width=7,height = 5,useDingbats=F)
Warning: File images_manuscript/fig_X_taxonomy_prediction.pdf already exists. File not overwritten.

Let’s now try to understand the relationship between number of samples available for a label and its accuracy. For each label, we will summarize the N in training set, precision, recall and F1 score in the validation set. Then we will plot the relationship between number of training samples and validation metrics.

summarize_comparison_with_value <- function(actual, predicted) {
  
  actual <- actual %>% mutate(k_v = paste(key, value, sep = ":"))
  predicted <- predicted %>% mutate( k_v = paste(key, value, sep = ":"))
  
  # Get the unique keys from both actual and predicted, excluding specific keys
  all_kv <- unique(c(actual$k_v, predicted$k_v))
  
  
  # Initialize a result dataframe
  result <- data.frame(
    k_v = all_kv,
    N_actual = integer(length(all_kv)),
    N_predicted = integer(length(all_kv)),
    TP = integer(length(all_kv)),
    FN = integer(length(all_kv)),
    FP = integer(length(all_kv)),
    stringsAsFactors = FALSE
  )
  
  for (k_v in all_kv) {
    actual_values <- actual$value[actual$k_v == k_v]
    predicted_values <- predicted$value[predicted$k_v == k_v]
    
    N_actual <- length(actual_values)
    N_predicted <- length(predicted_values)
    TP <- sum(actual_values %in% predicted_values)
    FN <- sum(!actual_values %in% predicted_values)
    FP <- sum(!predicted_values %in% actual_values)
    
    result[result$k_v == k_v, ] <- list(k_v, N_actual, N_predicted, TP, FN, FP)
  }
  
  return(result)
}
collate_results_with_value <- function(predictions_df) {
  predictions_df = predictions_df %>%
    select(sample_id, query_basepairs, query_mapping, actual, predicted, platform, library_strategy)
  # Apply the summarize_comparison function to each row
  results_list <- pmap(predictions_df, function(sample_id, query_basepairs, query_mapping, actual, predicted, platform, library_strategy) {
    result <- summarize_comparison_with_value(actual, predicted)
    result$sample_id <- sample_id
    result$query_basepairs <- query_basepairs
    result$query_mapping <- query_mapping
    result$platform = platform
    result$library_strategy = library_strategy
    return(result)
  })
  
  # Combine all results into a single dataframe
  results_df <- bind_rows(results_list)
  return(results_df)
}
summaries_kv = collate_results_with_value(vk_cgr_df)
summaries_kv
metrics_by_label = summaries_kv %>%
  select(-sample_id,-query_basepairs) %>%
  group_by(k_v) %>%
  summarise(across(where(is.numeric), sum)) %>%
  mutate(
    microprecision = TP / (TP + FP),
    microrecall = TP / (TP + FN),
    F1 = 2 * (microprecision * microrecall) / (microprecision + microrecall)
  ) %>%
  replace_na(list(microprecision = 0, microrecall = 0, F1 = 0))
  
metrics_by_label

Now we only need to count the occurrences of each label in the training set

metrics_by_label = read_csv('all_SRA_taxa/labels_ML_training.csv') %>%
  filter(!sample %in% vk_cgr_df$sample_id) %>%
  pull(labels) %>%
  str_split(';') %>%
  unlist %>%
  table %>%
  as_tibble(column_name='k_v',n="N_training") %>%
  rename(k_v = ".") %>%
  right_join(metrics_by_label) %>%
  pivot_longer(microprecision:F1,names_to = 'metric',values_to = 'value') %>%
  arrange(N_training) 
Rows: 230644 Columns: 2── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (2): sample, labels
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(k_v)`
metrics_by_label
metrics_summary <- metrics_by_label %>%
  filter(metric !='F1') %>%
  # Create log-scaled bins
  mutate(bin = cut(log10(N_training), breaks = 30)) %>%
  group_by(bin, metric) %>%
  summarise(
    median = median(value),
    q25 = quantile(value, 0.25),
    q75 = quantile(value, 0.75),
    # Get middle of the bin for plotting
    x = median(N_training),
    N=n()
  )
`summarise()` has grouped output by 'bin'. You can override using the `.groups` argument.
metrics_summary 

ggplot() +
  geom_bin2d(data = metrics_by_label %>% filter(metric !='F1'), aes(x = N_training, y = value)) +
  #geom_ribbon(data = metrics_summary,
  #            aes(x = x, ymin = q25, ymax = q75),
  #           alpha = 0.2,
  #            fill = "blue") +
  geom_line(data = metrics_summary,
            aes(x = x, y = median),
            color = "orange",
            linewidth = 1) +
    #geom_point(data = metrics_by_label %>% filter(metric !='F1'), 
    #          aes(x = N_training, y = value),
    #          alpha = 0.1) +
  scale_x_log10() +
  scale_fill_viridis_c(trans="log",name="Label counts",breaks=c(1,3,10,30,100,300,1000)) +
  facet_wrap(~metric, labeller = labeller(metric=c('microprecision'='Precision','microrecall'='Recall'))) +
  theme_minimal() +
  labs(
    x = "Number of Training Examples (log scale)",
    y = "Value",
    title = "Validation Metrics by Training Set Size"
  )


safe_ggsave ('images_manuscript/fig_X_n_train.pdf',width=7,height = 2.5,useDingbats=F)
Warning: File images_manuscript/fig_X_n_train.pdf already exists. File not overwritten.

Let’s now get some numbers for publication. What is the average precision at species level?

plot_df %>%
  filter(key=='Taxonomy_species',
         metric %in% c('precision','recall'),
         query_mapping=='varKode') %>%
  group_by(metric,platform) %>%
  summarise(mean_value=mean(metric_value))
`summarise()` has grouped output by 'metric'. You can override using the `.groups` argument.
NA

Genus level

plot_df %>%
  filter(key=='Taxonomy_genus',
         metric %in% c('precision','recall'),
         query_mapping=='varKode') %>%
  group_by(metric,platform) %>%
  summarise(mean_value=mean(metric_value))
`summarise()` has grouped output by 'metric'. You can override using the `.groups` argument.
NA

Family level

plot_df %>%
  filter(key=='Taxonomy_family',
         metric %in% c('precision','recall'),
         query_mapping=='varKode') %>%
  group_by(metric,platform) %>%
  summarise(mean_value=mean(metric_value))
`summarise()` has grouped output by 'metric'. You can override using the `.groups` argument.

All taxonomy

plot_df %>%
  filter(key=='Taxonomy_all',
         metric %in% c('precision','recall'),
         query_mapping=='varKode') %>%
  group_by(metric,platform) %>%
  summarise(mean_value=mean(metric_value))
`summarise()` has grouped output by 'metric'. You can override using the `.groups` argument.

Generating numbers for publication

Here we just query our results to get a few figures that we report in the paper.

Total number of samples used in cross-validation:

dim(samp_labels)
[1] 287   2

Number of Stigmaphyllon samples with each kind of error for varkoder:

summary_species

Number of Stigmaphyllon samples with each kind of error for skmer:

skmer_summary_species

Traditional barcode accuracy for species:

barcode_summary_species %>% arrange(query_bp,marker)

Concatenated barcode accuract for species:

concat_summary_species

varKoder accuracy for genera:

summary_genus

varKoder accuracy for family:

summary_family

Skmer accuracy for genera:

skmer_summary_genus

Skmer accuracy for family:

skmer_summary_family

Number of samples available for each genus and data amount

results %>%
  mutate(genus = str_extract(actual_labels,"(?<=genus:)[^;]+")) %>%
  group_by(query_bp) %>%
  summarize(N=n()) %>%
  complete()

Plot number of samples for supplementary material.

n_samples_genera = results %>%
  mutate(taxon = str_extract(actual_labels,"(?<=genus:)[^;]+")) %>%
  group_by(taxon, query_bp) %>%
  summarize(N=n()) %>%
  ungroup() %>%
  complete(taxon, query_bp, fill = list(N=0)) %>%
  mutate(taxon = fct_reorder(taxon, N))
`summarise()` has grouped output by 'taxon'. You can override using the `.groups` argument.
n_samples_genera 

n_samples_species = results %>%
  mutate(taxon = str_extract(actual_labels,"(?<=species:)[^;]+")) %>%
  filter(!is.na(taxon)) %>%
  group_by(taxon, query_bp) %>%
  summarize(N=n()) %>%
  ungroup() %>%
  complete(taxon, query_bp, fill = list(N=0))  %>%
  mutate(taxon = fct_reorder(taxon, N))
`summarise()` has grouped output by 'taxon'. You can override using the `.groups` argument.
n_samples_species 

For SRA eukaryotes, we have to count both validation and training samples, since we did not do cross-validation. Let’s use image names to get the information and then the results table to figure out which ones were in the validation set.

all_files = c(list.files('all_SRA_eukaryote_families/varkoder_images_SRA/',pattern='*.png',recursive = T),
              list.files('all_SRA_eukaryote_familiesvarkoder_query_images/',pattern='*.png',recursive = T))

n_samples_SRA = data.frame(filename=all_files) %>%
  mutate(
    sample_id = str_extract(filename, "^(.+)(?=@)"),  # Capture everything up to the "@" symbol but exclude the symbol itself
    query_bp = str_extract(filename, "(?<=@)([0-9]+)K") # multiply by 1000 to convert K to the actual number
  ) %>% 
  left_join(read_csv('all_SRA_eukaryote_families/runs_to_download_data.csv') %>% 
              select(sample_id=Run,Kingdom,taxon=FamilyID)) %>%
  mutate_at(vars(taxon),as.character) %>%
  mutate(validation_set = sample_id %in% varKoder_SRA_results$sample_id) %>%
  group_by(taxon, query_bp,validation_set) %>%
  summarize(N=n()) %>%
  ungroup() %>%
  mutate(taxon = fct_reorder(taxon, N))
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 8264 Columns: 51── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr  (28): Run, AssemblyName, download_path, Experiment, LibraryName, Library...
dbl  (11): spots, bases, spots_with_mates, avgLength, size_MB, InsertSize, In...
lgl  (10): g1k_pop_code, source, g1k_analysis_group, Subject_ID, Disease, Aff...
dttm  (2): ReleaseDate, LoadDate
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(sample_id)``summarise()` has grouped output by 'taxon', 'query_bp'. You can override using the `.groups` argument.
n_samples_SRA
  

  ((list.files('all_SRA_eukaryote_familiesvarkoder_images_SRA/',pattern='*.png',recursive = T) %>%
      str_extract("^(.+)(?=@)"))%in%
varKoder_SRA_results$sample_id) %>% summary
   Mode 
logical 
plot_Nsamples_area = function(df, title){
  df = df #%>% 
   # mutate(query_bp = parse_number(query_bp) *1000)
  
  n_levels <- length(unique(df$taxon))
  viridis_colors <- viridis::turbo(n_levels)
  
  half_n <- ceiling(n_levels / 2)
  reordered_colors <- c(rbind(viridis_colors[1:half_n], viridis_colors[(half_n + 1):n_levels]))


  
  
  ggplot(df, aes(x=query_bp,y=N,fill=taxon, color = taxon, group = taxon)) +
    geom_area(position= position_stack()) +
    #geom_line(position='stack') +
    scale_fill_manual(values = reordered_colors, 
                      aesthetics = c('colour','fill'),
                      guide = 'none') +
    scale_x_log10(labels = scales::label_number(scale_cut = scales::cut_si('bp')),
                  breaks = unique(n_samples_genera$query_bp),
                  limits = range(unique(n_samples_genera$query_bp))) +
    scale_y_continuous(n.breaks = 10, minor_breaks = waiver()) +
    ggtitle(title) +
    ylab('Number of samples') +
    xlab('Base pairs in query images') +
    theme_few() +
    theme(axis.text.x = element_text(hjust=1,angle=45),
          panel.background = element_rect(fill = NA),
            panel.grid.major.y = element_line(colour = gray(0.5)),
            panel.grid.minor.y = element_line(colour = gray(0.6),linetype = 2),
            panel.ontop = TRUE)
}

N_species = plot_Nsamples_area(n_samples_species, title = expression(italic('Stigmaphyllon')~'species')) + theme(axis.title.x = element_blank(),text = element_text(size=8))
N_genera = plot_Nsamples_area(n_samples_genera, title = 'Malpighiales genera') + theme(axis.title.x = element_blank(),text = element_text(size=8))

p = plot_grid(N_species, N_genera, nrow = 1)

# Add common plot title with white background
common_title = ggdraw() + draw_label("Number of samples available for different data amounts", fontface = 'bold', x = 0.5, hjust = 0.5) + theme(plot.background = element_rect(fill = "white", color = "white"), plot.margin = unit(c(0, 0, 0, 0), "null"))
p = plot_grid(common_title, p, ncol = 1, rel_heights = c(0.1, 1))

# Add common X-axis title with white background
x_axis_title = ggdraw() + draw_label("Post-cleaning base pairs available", x = 0.5, hjust = 0.5, vjust = 1) + theme(plot.background = element_rect(fill = "white", color = "white"), plot.margin = unit(c(-1, 0, 0, 0), "lines"))
p = plot_grid(p, x_axis_title, ncol = 1, rel_heights = c(1, 0.1))


print(p)


safe_ggsave ('images_manuscript/supp_fig_n_samples.pdf', width=8,height = 4)
Warning: File images_manuscript/supp_fig_n_samples.pdf already exists. File not overwritten.
safe_ggsave ('images_manuscript/supp_fig_n_samples.png', width=8,height = 4,dpi = 1200)
Warning: File images_manuscript/supp_fig_n_samples.png already exists. File not overwritten.

Total number of SRA eukaryote family samples. Validation:

read_csv('all_SRA_eukaryote_families/varkoder_trained_model_ML/input_data.csv')[-1] %>%
  group_by(is_valid) %>%
  summarise(N = n())
New names:Rows: 41103 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (2): sample, path
dbl (3): ...1, bp, labels
lgl (2): possible_low_quality, is_valid
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

For SRA all taxa, let’s summarize the number of samples for each label. Let’s start by parsing the data. We will start with train_valid_sets.csv but then filter to the samples actually used. A few might have failed to produce varKodes.

plan(multisession, workers = 10)


all_SRA_samples = read_csv("all_SRA_taxa/train_valid_sets.csv") %>%
  split(1:nrow(.)) %>%  # Split the dataframe into a list of rows
  future_map_dfr( ~ {
    actual = unpack_labels(.x$labels)
    library_strategy = actual %>%
      filter(key == 'LibraryStrategy') %>%
      pull(value)
    platform = actual %>%
      filter(key == 'Platform') %>%
      pull(value)
    
    tibble(
      sample_id = .x$Run,
      labels = list(actual),
      library_strategy = library_strategy,
      platform = platform,
      is_valid = .x$is_valid
    )
  }, .options = furrr_options(seed = TRUE)
  )
Rows: 255159 Columns: 5── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (2): Run, labels
dbl (2): spots, AveSpotsTo20Mbp
lgl (1): is_valid
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
plan(sequential)

actual_tr = read_csv("all_SRA_taxa/varkodes_ViT_ML/input_data.csv")  %>% filter(is_valid==FALSE) %>% pull(sample) %>% unique
Rows: 1374558 Columns: 7── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (4): sample, img_kmer_mapping, path, labels
dbl (2): bp, img_kmer_size
lgl (1): is_valid
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
actual_vd = read_csv("all_SRA_taxa/results_varKodes/predictions.csv") %>% pull(sample_id) %>% unique
Rows: 152015 Columns: 12── Column specification ─────────────────────────────────────────────────────────
Delimiter: ","
chr (7): varKode_image_path, sample_id, query_mapping, trained_model_path, ac...
dbl (4): query_basepairs, query_kmer_len, basefrequency_sd, prediction_threshold
lgl (1): possible_low_quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
all_SRA_samples = all_SRA_samples%>% filter(sample_id %in% c(actual_tr,actual_vd))
all_SRA_samples
NA

Now let’s count the number of samples for each label

N_summary = all_SRA_samples %>%
  unnest(labels) %>%
  rename(label_key = key, label_value = value) %>%
  group_by(is_valid,library_strategy,platform,label_key,label_value) %>%
  summarize(N_samples = n(), .groups = "drop") %>%
  filter(label_key %in% c('Taxonomy_family','Taxonomy_species'))

N_summary

Now, for each combination of library strategy, platform and whether sample is validation, we will summarize the range, mean and median number of samples per label.

N_per_label_key = N_summary %>%
  group_by(is_valid, platform, library_strategy, label_key) %>%
  summarise(
    n_labels = n(),
    min = min(N_samples, na.rm = TRUE),
    q1 = quantile(N_samples, 0.25, na.rm = TRUE),
    median = median(N_samples, na.rm = TRUE),
    mean = mean(N_samples, na.rm = TRUE),
    q3 = quantile(N_samples, 0.75, na.rm = TRUE),
    max = max(N_samples, na.rm = TRUE)
  ) %>%
  ungroup()
`summarise()` has grouped output by 'is_valid', 'platform', 'library_strategy'. You can override using the `.groups` argument.
N_per_label_key

dir.create('tables_manuscript',showWarnings = F)
write_csv(N_per_label_key, 'tables_manuscript/summary_samples_all_SRA.csv')

Let’s now count the total number of unique taxonomy labels:

N_summary %>% 
  filter(str_detect(label_key,'^Taxonomy')) %>%
  pull(label_value) %>%
  unique %>%
  length()
[1] 14151

And the number of unique accessions:

all_SRA_samples$sample_id %>% unique %>% length()
[1] 254819

Precision and recall

all SRA

Pooled across sequencing methods for families and all

summaries_with_scores %>% filter(key %in% c('Taxonomy_family','Taxonomy_all')) %>% group_by(key) %>%
  summarise(min_pr=min(microprecision),
            max_pr=max(microprecision),
            min_rec=min(microrecall),
            max_rec=max(microrecall))

Discriminating sequencing methods for genera and species

summaries_with_scores %>% 
  filter(key %in% c('Taxonomy_genus','Taxonomy_species'),
         query_basepairs == 10000000) %>% 
  arrange(key,desc(microprecision))

Now let’s compare averages across mapping for species level.

summaries_with_scores %>% 
  filter(key %in% c('Taxonomy_species')) %>% 
  group_by(query_mapping) %>%
  summarise(mean(microprecision), mean(microrecall))

Other datasets

Calculate micro-averaged precision and recall.


calculate_precision_recall = function(results, taxonomic_level=NULL) {
  # Function to filter labels by taxonomic level
  filter_labels <- function(labels_list, level) {
    if (is.null(level)) {
      return(labels_list)
    } else {
      return(grep(paste0("^", level, ":"), labels_list, value = TRUE))
    }
  }

  # Filter rows and labels for a given taxonomic level
  filter_rows_and_labels <- function(results, level) {
    if (is.null(level)) {
      return(results)
    } else {
      # Keep only rows where the level is found in query_labels
      filtered_results <- results[sapply(results$query_labels, function(x) {
        any(grepl(paste0("^", level, ":"), x))
      }), ]

      # Filter labels in both query_labels and predicted_list
      filtered_results$query_labels <- lapply(filtered_results$query_labels, filter_labels, level)
      filtered_results$predicted_list <- lapply(filtered_results$predicted_list, filter_labels, level)

      return(filtered_results)
    }
  }

  # Apply filtering
  filtered_results <- filter_rows_and_labels(results, taxonomic_level)

  # Initialize counters for true positives, false positives, and false negatives
  total_true_positives <- 0
  total_false_positives <- 0
  total_false_negatives <- 0

  # Process each row in the filtered results
  for (i in seq_len(nrow(filtered_results))) {
    query_labels <- filtered_results$query_labels[[i]]
    predicted_labels <- filtered_results$predicted_list[[i]]

    true_positives <- sum(predicted_labels %in% query_labels)
    false_positives <- sum(!predicted_labels %in% query_labels & !is.na(predicted_labels) & predicted_labels != "")
    false_negatives <- sum(!query_labels %in% predicted_labels & !is.na(query_labels) & query_labels != "")

    # Update aggregate counts
    total_true_positives <- total_true_positives + true_positives
    total_false_positives <- total_false_positives + false_positives
    total_false_negatives <- total_false_negatives + false_negatives
  }

  # Calculate micro-averaged precision and recall
  micro_precision <- ifelse((total_true_positives + total_false_positives) > 0, 
                            total_true_positives / (total_true_positives + total_false_positives), 
                            NA_real_)
  micro_recall <- ifelse((total_true_positives + total_false_negatives) > 0, 
                         total_true_positives / (total_true_positives + total_false_negatives), 
                         NA_real_)

  return(tibble(taxonomic_level = taxonomic_level, micro_precision = micro_precision, micro_recall = micro_recall))
}

Malpighiales

Precision and recall for species:

filter(results,str_detect(actual_labels,'species')) %>%
         split(.$query_bp) %>%
         map_dfr(~calculate_precision_recall(.x,'species'),.id='query_bp')

Precision and recall for genera:

filter(results,str_detect(actual_labels,'genus')) %>%
         split(.$query_bp) %>%
         map_dfr(~calculate_precision_recall(.x,'genus'),.id='query_bp')

Precision and recall for families:

filter(results,str_detect(actual_labels,'family')) %>%
         split(.$query_bp) %>%
         map_dfr(~calculate_precision_recall(.x,'family'),.id='query_bp')

SRA eukaryotes

Precision and recall for SRA eukaryote families, rfCGR representation:

pr_rc_euk_SRA = varKoder_SRA_results %>%
  split(.$query_bp) %>%
         map_dfr(~calculate_precision_recall(.x),.id='query_bp') %>%
  mutate(query_bp = 1000*(str_remove(query_bp,'K') %>% as.numeric))
pr_rc_euk_SRA

Precision and recall for SRA eukaryote families, varKode representation:

pr_rc_euk_SRA_vk = varKoder_SRA_results_vk %>%
  split(.$query_bp) %>%
         map_dfr(~calculate_precision_recall(.x),.id='query_bp') %>%
  mutate(query_bp = 1000*as.integer(query_bp))
pr_rc_euk_SRA_vk

Let’s join both tables:

full_join(pr_rc_euk_SRA,pr_rc_euk_SRA_vk,by="query_bp",suffix = c('.varKode','.rfCGR'))

Precision and recall for other taxa, varKode representation:

other_results %>%
  filter(in_training_model == 'yes') %>%
  split(.$dataset) %>%
  map_dfr(~calculate_precision_recall(.x),.id='dataset')

Precision and recall for other taxa, CGR representation:

other_results_CGR %>%
  filter(in_training_model == 'yes') %>%
  split(.$dataset) %>%
  map_dfr(~calculate_precision_recall(.x),.id='dataset')

Numbers correct and icorrect, CGR:

summary_others_CGR %>% group_by(dataset,result,taxon_in_training) %>% summarise(N=sum(N),p=mean(p)) 
`summarise()` has grouped output by 'dataset', 'result'. You can override using the `.groups` argument.

Precision and recall for conventional barcodes at species level

results_barcodes %>%
  group_by(marker,query_bp) %>%
  nest() %>%
  mutate(precision_recall = purrr::map(data, calculate_precision_recall,'species')) %>%
  select(-data) %>%
  unnest(precision_recall)

Precision and recall for concatenated conventional barcodes at species level

results_concat_barcodes %>%
  split(.$query_bp) %>%
  map_dfr(~calculate_precision_recall(.x),.id='query_bp','species')

Precision/recall for skmer at species level. Precision and recall are not always the same because in some cases skmer predicted the wrong genus, and, therefore, there was no species prediction.

skmer_results_df %>%
  split(.$query_bp) %>%
  map_dfr(~calculate_precision_recall(.x,'species'),.id='query_bp')

Precision and recall for conventional barcodes at genus level

results_barcodes %>%
  group_by(marker,query_bp) %>%
  nest() %>%
  mutate(precision_recall = purrr::map(data, calculate_precision_recall,'genus')) %>%
  select(-data) %>%
  unnest(precision_recall)

Precision and recall for concatenated conventional barcodes at genus level

results_concat_barcodes %>%
  split(.$query_bp) %>%
  map_dfr(~calculate_precision_recall(.x),.id='query_bp','genus')

Precision/recall for skmer at genus level:

skmer_results_df %>%
  split(.$query_bp) %>%
  map_dfr(~calculate_precision_recall(.x,'genus'),.id='query_bp')

Precision and recall for conventional barcodes at family level

results_barcodes %>%
  group_by(marker,query_bp) %>%
  nest() %>%
  mutate(precision_recall = purrr::map(data, calculate_precision_recall,'family')) %>%
  select(-data) %>%
  unnest(precision_recall)

Precision and recall for concatenated conventional barcodes at family level

results_concat_barcodes %>%
  split(.$query_bp) %>%
  map_dfr(~calculate_precision_recall(.x),.id='query_bp','family')

Precision/recall for skmer at family level:

skmer_results_df %>%
  split(.$query_bp) %>%
  map_dfr(~calculate_precision_recall(.x,'family'),.id='query_bp')
LS0tCnRpdGxlOiAiVmFyS29kZXIgdGVzdGluZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVG8gY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdmFyS29kZSB0byBbU2ttZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaGFoYWItc2FybWFzaGdoaS9Ta21lciksIHdlIHdpbGwgdXNlIGxlYXZlLW9uZS1vdXQgY3Jvc3MgdmFsaWRhdGlvbjogd2UgcmVtb3ZlIG9uZSBzYW1wbGUgZnJvbSB0aGUgZGF0YXNldCwgdHJhaW4gYSB2YXJLb2RlIG1vZGVsIG9yIG1ha2UgYSBza21lciByZWZlcmVuY2Ugd2l0aCB0aGUgcmVtYWluaW5nIHNhbXBsZXMsIGFuZCB0aGVuIHVzZSB0aGUgc2FtcGxlIGxlZnQgb3V0IGFzIHF1ZXJ5LiBXZSB0aGVuIHJlY29yZCB3aGV0aGVyIG9yIG5vdCB3ZSBjb3JyZWN0bHkgaWRlbnRpZnkgdGhpcyBzYW1wbGUgaW4gdmFyS29kZXIsIGFuZCB3aGV0aGVyIG9yIG5vdCB0aGUgY2xvc2VzdCBzYW1wbGUgd2l0aCBTa21lciBoYXMgdGhlIHNhbWUgaWRlbnRpZmljYXRpb24uIAoKRm9yIHRyYWRpdGlvbmFsIGJhcmNvZGVzLCB3ZSBhc3NlbWJsZWQgdGhlIGdlbm9tZSBvZiBlYWNoIHNhbXBsZSwgYW5kIHRoZW4gdXNlZCBCTEFTVCB0byBzZWFyY2ggZm9yIGVhY2ggb2YgdGhlIHRyYWRpdGlvbmFsIGJhcmNvZGUgZ2VuZXMuIFdlIHJlY29yZGVkIGlmIHdlIGNvdWxkIGZpbmQgdGhpcyBnZW5lIGluIHRoZSBhc3NlbWJseSwgY29kaW5nIGFzIG1pc3NpbmcgZGF0YSBpZiB3ZSBjb3VsZCBub3QuIFdlIHRoZW4gcmVjb3JkZWQgd2hldGhlciB0aGUgYmVzdCBCTEFTVCBoaXQgZm9yIGEgc2FtcGxlIHdhcyB0aGUgY29ycmVjdCBzcGVjaWVzLgoKYGBge3J9CiNybShsaXN0PWxzKCkpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGZ1dHVyZSkKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkocGh5dG9vbHMpCmxpYnJhcnkoYXBlKQpsaWJyYXJ5KGZ1cnJyKQpzZXQuc2VlZCgxNDE2NCkKCiNmdW5jdGlvbiB0byBwcmV2ZW50IG92ZXJ3cml0aW5nIHBsb3RzCnNhZmVfZ2dzYXZlIDwtIGZ1bmN0aW9uKGZpbGVuYW1lLCAuLi4pIHsKICBpZiAoZmlsZS5leGlzdHMoZmlsZW5hbWUpKSB7CiAgICB3YXJuaW5nKHBhc3RlKCJGaWxlIiwgZmlsZW5hbWUsICJhbHJlYWR5IGV4aXN0cy4gRmlsZSBub3Qgb3ZlcndyaXR0ZW4uIikpCiAgICByZXR1cm4oaW52aXNpYmxlKE5VTEwpKQogIH0KICBnZ3NhdmUoZmlsZW5hbWUsIC4uLikKfQoKYGBgCiMgVmFyS29kZXIKRm9yIFZhcktvZGVyLCB3ZSB1c2VkIGxlYXZlLW9uZS1vdXQgY3Jvc3MtdmFsaWRhdGlvbiB0byB0ZXN0IHRoZSBhY2N1cmFjeSBmb3IgZmFtaWx5LCBnZW5lcmEsIHNwZWNpZXMgaW4gdGhlIGpvaW50IE1hbHBpZ2hpYWNlYWUtQ2hyeXNvYmFsYW5hY2VhZSBkYXRhc2V0LiBXZSB1c2VkIGFzIGlucHV0IGRhdGEgdmFyS29kZXMgcHJvZHVjZWQgZnJvbSBrbWVycyBvZiBzaXplIDcgYW5kIDUwMEticCB0byAyMDBNYnAgb2YgZGF0YSwgb3IgYWxsIG9mIHRoZSBkYXRhIGF2YWlsYWJsZSBpZiBsZXNzIHRoYW4gMjAwIE1icC4gRm9yIGVhY2ggc2FtcGxlLCB3ZSBidWlsdCBhIG1vZGVsIHVzaW5nIGFzIGlucHV0IGRhdGEgZnJvbSBhbGwgb3RoZXIgc2FtcGxlcy4gVGhlbiB3ZSBxdWVyaWVkIHRoZSBzYW1wbGUgbGVmdCBvdXQsIHVzaW5nIGFzIGlucHV0IHRoZSBpbWFnZXMgZ2VuZXJhdGVkIGZyb20gNTAwS2IgdG8gdGhlIHRvdGFsIGRhdGEgYXZhaWxhYmxlLiBOb3cgd2Ugd2lsbCBzdW1tYXJpemUgdGhlIHJlc3VsdHMuCgoKIyMgQWNjdXJhY3kgdnMgZGF0YSBhbW91bnQgYW5kIHRheG9ub21pYyBsZXZlbHMKCkluIHRoaXMgdGVzdCwgd2UgdXNlZCB2YXJLb2RlciBbdjAuOC4wXShodHRwczovL2dpdGh1Yi5jb20vYnJ1bm9hc20vdmFyS29kZXIvcmVsZWFzZXMvdGFnL3YuMC44LjApLiBMZXQncyBwcm9jZXNzIHRoZSByZXN1bHRzLgoKYGBge3J9CnJlYWRfYW5kX3Byb2Nlc3NfeHZhbCA9IGZ1bmN0aW9uKGluZm9sZGVyKXsKICBwbGFuKG11bHRpc2Vzc2lvbih3b3JrZXJzID0gMTIpKQp2YXJrb2Rlcl9yZXN1bHRzID0gbGlzdC5maWxlcyhpbmZvbGRlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncHJlZGljdGlvbnMuY3N2JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWN1cnNpdmU9VCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVCkgJT4lCiAgZnVycnI6OmZ1dHVyZV9tYXBfZGZyKH5yZWFkX2NzdigueCkgJT4lIG11dGF0ZShzYW1wbGVfaWQgPSBhcy5jaGFyYWN0ZXIoc2FtcGxlX2lkKSkpICU+JSAKICBzZWxlY3QoLTEpICU+JQogIG11dGF0ZShxdWVyeV9iYXNlcGFpcnMgPSBjYXNlX3doZW4oCiAgICBpcy5jaGFyYWN0ZXIocXVlcnlfYmFzZXBhaXJzKSB+IGFzLm51bWVyaWMoc3RyX3JlbW92ZShxdWVyeV9iYXNlcGFpcnMsICJLIikpICogMTAwMCwKICAgIFRSVUUgfiBhcy5udW1lcmljKHF1ZXJ5X2Jhc2VwYWlycykKICApKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChmb3JtYXQocXVlcnlfYmFzZXBhaXJzLCBzY2llbnRpZmljID0gRkFMU0UsdHJpbSA9IFRSVUUpLCAiXlsxMjVdMCskIikpICU+JSAjd2Ugd2lsbCBpZ25vcmUgcXVlcmllcyB0aGF0IGFyZSBub3Qgc3RhbmRhcmRpemVkIHNpemVzCiAgcmVuYW1lKHF1ZXJ5X2JwID0gcXVlcnlfYmFzZXBhaXJzKSAlPiUKICBtdXRhdGUocXVhbGl0eV9pbmNsdWRlZCA9IFQpCnBsYW4oc2VxdWVudGlhbCkKCmFsbF90YXhsYWJlbHMgPSBzdHJfcmVtb3ZlKHZhcmtvZGVyX3Jlc3VsdHMkYWN0dWFsX2xhYmVscywiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JykgJT4lIHVubGlzdCAlPiUgdW5pcXVlCgp2YXJrb2Rlcl9yZXN1bHRzID0gdmFya29kZXJfcmVzdWx0cyAlPiUKICBtdXRhdGUocXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSwKICAgICAgICAgcHJlZGljdGVkX2xpc3QgPSBzdHJfc3BsaXQocHJlZGljdGVkX2xhYmVscywnOycpCiAgICAgICAgICkgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZShmYW1pbHlfY29ycmVjdCA9IHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywnZmFtaWx5JyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgICAgIGdlbnVzX2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsJ2dlbnVzJyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgICAgIHNwZWNpZXNfY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsJ3NwZWNpZXMnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsJ3NwZWNpZXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICBmYW1pbHlfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwnZmFtaWx5JyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCdmYW1pbHknKV0pKSwKICAgICAgICAgZ2VudXNfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwnZ2VudXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsJ2dlbnVzJyldKSksCiAgICAgICAgIHNwZWNpZXNfaW5jb3JyZWN0ID0gaWZlbHNlKGFueShzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywnc3BlY2llcycpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFueSghKHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsJ3NwZWNpZXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsJ3NwZWNpZXMnKV0pKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgIAogICAgICAgICApCgpyZXR1cm4odmFya29kZXJfcmVzdWx0cykKfQpgYGAKCgpgYGB7cn0Kc3VtbWFyaXplX3Jlc3VsdHMgPSBmdW5jdGlvbihyZXMsbGV2ZWwpewogIHJlcyA9IHJlcyAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIG11dGF0ZShsb3dfcXVhbGl0eSA9IHN0cl9kZXRlY3QoYWN0dWFsX2xhYmVscywibG93X3F1YWxpdHk6VHJ1ZSIpLAogICAgICAgICAgIHJlc3VsdCA9IGFzLmNoYXJhY3RlcihpZmVsc2UocmVzWyxzdHJfYyhsZXZlbCwnY29ycmVjdCcsc2VwPSdfJyldICYgIXJlc1ssc3RyX2MobGV2ZWwsJ2luY29ycmVjdCcsc2VwPSdfJyldLCAnY29ycmVjdCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShyZXNbLHN0cl9jKGxldmVsLCdjb3JyZWN0JyxzZXA9J18nKV0gJiByZXNbLHN0cl9jKGxldmVsLCdpbmNvcnJlY3QnLHNlcD0nXycpXSwgJ2FtYmlndW91cycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoIXJlc1ssc3RyX2MobGV2ZWwsJ2NvcnJlY3QnLHNlcD0nXycpXSAgJiByZXNbLHN0cl9jKGxldmVsLCdpbmNvcnJlY3QnLHNlcD0nXycpXSwgJ2luY29ycmVjdCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnaW5jb25jbHVzaXZlJwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkpKQogICAgICAgICAgICkgJT4lCiAgICBmaWx0ZXIoIWlzLm5hKHJlc3VsdCkpICU+JQogICAgZ3JvdXBfYnkocXVlcnlfYnAscmVzdWx0KSAlPiUKICAgIHN1bW1hcmlzZShOPW4oKSwgLmdyb3VwcyA9ICdkcm9wJykgJT4lCiAgICBncm91cF9ieShxdWVyeV9icCkgJT4lCiAgICBtdXRhdGUocD0gTi9zdW0oTikpICU+JQogICAgI211dGF0ZShxdWVyeV9icCA9IGFzLmludGVnZXIoc3RyX3JlbW92ZShxdWVyeV9icCwnSycpKSoxMDAwKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGFzLmZhY3RvcihxdWVyeV9icCkpICU+JQogICAgY29tcGxldGUocXVlcnlfYnAscmVzdWx0LCBmaWxsID0gbGlzdChwID0gMCwgTiA9IDApKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHF1ZXJ5X2JwKSkpICU+JQogICAgdW5ncm91cCgpCiAgICAKICByZXR1cm4ocmVzKQp9CmBgYAoKCmBgYHtyfQpwbG90X2FyZWEgPSBmdW5jdGlvbihzdW1fZGYsIHRpdGxlLCByZWxhdGl2ZSA9IEZBTFNFLCBncmlkID0gVFJVRSwgeGxpbV9hbGwgPSBUUlVFLCB3cmFwKXsKICBicmVha3MgPSBjKDUwMDAwMCwKICAgICAgICAgICAgIDEwMDAwMDAsCiAgICAgICAgICAgICAyMDAwMDAwLAogICAgICAgICAgICAgNTAwMDAwMCwKICAgICAgICAgICAgIDEwMDAwMDAwLAogICAgICAgICAgICAgMjAwMDAwMDAsCiAgICAgICAgICAgICA1MDAwMDAwMCwKICAgICAgICAgICAgIDEwMDAwMDAwMCwKICAgICAgICAgICAgIDIwMDAwMDAwMAogICAgICAgICAgICAgKQogIGlmICh4bGltX2FsbCl7CiAgICB4bGltaXRzID0gcmFuZ2UoYnJlYWtzKQogIH0gZWxzZSB7CiAgICB4bGltaXRzID0gcmFuZ2Uoc3VtX2RmJHF1ZXJ5X2JwKQogIH0KICAKICAKICBzdW1fZGYgPSBzdW1fZGYgJT4lCiAgICBtdXRhdGUocmVzdWx0ID0gZmFjdG9yKHJlc3VsdCxvcmRlcmVkID0gVCwgbGV2ZWxzID0gYygnY29ycmVjdCcsJ2FtYmlndW91cycsJ2luY29uY2x1c2l2ZScsJ2luY29ycmVjdCcpKSkgCiAgaWYgKHJlbGF0aXZlKXsKICAgIHlsaW1pdHMgPSBjKDAsMSkKICB9IGVsc2UgewogICAgeWxpbWl0cyA9IGMoMCxzdW1fZGYgJT4lIGdyb3VwX2J5KHF1ZXJ5X2JwKSAlPiUgc3VtbWFyaXplKE49c3VtKE4pKSAlPiUgcHVsbChOKSAlPiUgbWF4KQogIH0KICAKICAKICAjIEdldCBjb2xvcnMgZnJvbSBhIENvbG9yIEJyZXdlciBwYWxldHRlCiAgYnJld2VyX2NvbG9ycyA8LSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNCwgIkFjY2VudCIpCiAgCiAgaWYgKHJlbGF0aXZlKSB7CiAgICBwMSA9IGdncGxvdChzdW1fZGYsIGFlcyh4PXF1ZXJ5X2JwLHk9cCxmaWxsPXJlc3VsdCkpICsKICAgIGdlb21fYXJlYShwb3NpdGlvbj0nc3RhY2snKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBzZXROYW1lcyhicmV3ZXJfY29sb3JzLCBjKCJjb3JyZWN0IiwgImFtYmlndW91cyIsICJpbmNvbmNsdXNpdmUiLCAiaW5jb3JyZWN0IikpKSArCiAgICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzPWMoMC41LDEpKSArCiAgICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKHNjYWxlX2N1dCA9IHNjYWxlczo6Y3V0X3NpKCdicCcpKSxicmVha3MgPSBicmVha3MpICArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoKSArCiAgICBnZ3RpdGxlKHRpdGxlKSArCiAgICB5bGFiKCdGcmFjdGlvbiBvZiBzYW1wbGVzJykgKwogICAgeGxhYignQmFzZSBwYWlycyBpbiBxdWVyeSBpbWFnZXMnKSArCiAgICB0aGVtZV9mZXcoKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChoanVzdD0xLGFuZ2xlPTQ1KSkKICB9IGVsc2UgewogICAgICBwMSA9IGdncGxvdChzdW1fZGYsIGFlcyh4PXF1ZXJ5X2JwLHk9TixmaWxsPXJlc3VsdCkpICsKICAgIGdlb21fYXJlYShwb3NpdGlvbj0nc3RhY2snKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBzZXROYW1lcyhicmV3ZXJfY29sb3JzLCBjKCJjb3JyZWN0IiwgImFtYmlndW91cyIsICJpbmNvbmNsdXNpdmUiLCAiaW5jb3JyZWN0IikpKSArCiAgICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzPWMoMC41LDEpKSArCiAgICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKHNjYWxlX2N1dCA9IHNjYWxlczo6Y3V0X3NpKCdicCcpKSxicmVha3MgPSBicmVha3MpICAgKwogICAgc2NhbGVfeV9jb250aW51b3VzKCkgKwogICAgZ2d0aXRsZSh0aXRsZSkgKwogICAgeWxhYignTnVtYmVyIG9mIHNhbXBsZXMnKSArCiAgICB4bGFiKCdCYXNlIHBhaXJzIGluIHF1ZXJ5IGltYWdlcycpICsKICAgIHRoZW1lX2ZldygpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGhqdXN0PTEsYW5nbGU9NDUpKQogIH0KICAKICBpZiAoZ3JpZCl7CiAgICBwMSA9IHAxICsKICAgICAgc2NhbGVfeV9jb250aW51b3VzKG4uYnJlYWtzID0gMTAsIG1pbm9yX2JyZWFrcyA9IHdhaXZlcigpKSArCiAgICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSwKICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC41KSksCiAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBncmF5KDAuNiksbGluZXR5cGUgPSAyKSwKICAgICAgICAgICAgcGFuZWwub250b3AgPSBUUlVFKQogIH0KICAKICBwMSA9IHAxICsgY29vcmRfY2FydGVzaWFuKHhsaW09eGxpbWl0cywgeWxpbT15bGltaXRzLGV4cGFuZCA9IEZBTFNFKQogIAogIGlmICghbWlzc2luZyh3cmFwKSkgewogICAgcDEgPSBwMSArIGZhY2V0X3dyYXAoYXMuZm9ybXVsYSh3cmFwKSkKICB9CiAgCiAgcmV0dXJuKHAxKQp9CiAgCmBgYAoKCk5vdyBsZXQncyBwbG90IGdlbnVzLWxldmVsIGFjY3VyYWN5OgoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVzdWx0cyA9IHJlYWRfYW5kX3Byb2Nlc3NfeHZhbCgnYWRkaXRpb25hbF90ZXN0c19jZ3IvcmVzdWx0c19jZ3JfdmFyS29kZXJfdml0X2xhcmdlX3BhdGNoMzJfMjI0LycpCnN1bW1hcnlfZ2VudXMgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzLCdnZW51cycpCnBfZ2VudXMgPSBwbG90X2FyZWEoc3VtbWFyeV9nZW51cywgJ3ZhcktvZGVyIGdlbnVzJywgcmVsYXRpdmUgPSBUUlVFKQpwX2dlbnVzCmBgYApOb3cgdGhlIHNhbWUgYnV0IHdpdGggc3BlY2llcwpgYGB7cn0Kc3VtbWFyeV9zcGVjaWVzID0gc3VtbWFyaXplX3Jlc3VsdHMocmVzdWx0cywnc3BlY2llcycpCnBfc3BlY2llcyA9IHBsb3RfYXJlYShzdW1tYXJ5X3NwZWNpZXMsICd2YXJLb2RlciBzcGVjaWVzJywgcmVsYXRpdmUgPSBUUlVFKQpwX3NwZWNpZXMKYGBgCgpGaW5hbGx5LCBmYW1pbHkKYGBge3J9CnN1bW1hcnlfZmFtaWx5ID0gc3VtbWFyaXplX3Jlc3VsdHMocmVzdWx0cywnZmFtaWx5JykKcF9mYW1pbHkgPSBwbG90X2FyZWEoc3VtbWFyeV9mYW1pbHksICd2YXJLb2RlciBmYW1pbHknLCByZWxhdGl2ZSA9IFRSVUUpCnBfZmFtaWx5CmBgYAojIyB3aGF0IGV4cGxhaW5zIHRoZSBlcnJvcnM/CgpOb3cgd2Ugd2lsbCB0cnkgdG8gaWRlbnRpZnkgd2hpY2ggc2FtcGxlcyBmYWlsZWQgYW5kIHdoeSB0aGV5IGZhaWxlZC4gUGFydGljdWFybHksIGhvdyBkbyAKRE5BIHF1YWxpdHksIGFtb3VudCBvZiBkYXRhLCBhbmQgdGhlIG51bWJlciBvZiBzYW1wbGVzIHBlciBjbGFzcyBpbXBhY3QgcmVzdWx0cz8gV2Ugd2lsbCB1c2UgZ2VudXMtbGV2ZWwgcHJlZGljdGlvbnMgdG8gdGVzdC4KCmBgYHtyfQpnZW51c19wcmVkaWN0aW9ucyA9IHJlc3VsdHMgJT4lCiAgbXV0YXRlKHByZWRpY3RlZF9nZW51cyA9IHN0cl9leHRyYWN0KHByZWRpY3RlZF9sYWJlbHMsICdnZW51czpbXjtdKicpLAogICAgICAgICBhY3R1YWxfZ2VudXMgPSBzdHJfZXh0cmFjdChhY3R1YWxfbGFiZWxzLCAnZ2VudXM6W147XSonKSkgJT4lCiAgc2VsZWN0KC1zdGFydHNfd2l0aCgnZmFtaWx5JyksLXN0YXJ0c193aXRoKCdzcGVjaWVzJykpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgoImdlbnVzIiksIG5hbWVzX3RvID0gInByZWRpY3RlZF9sYWJlbCIsIHZhbHVlc190byA9ICJjb25maWRlbmNlIikgJT4lCiAgZmlsdGVyKGFjdHVhbF9nZW51cyA9PSBwcmVkaWN0ZWRfbGFiZWwpICU+JQogIHNlbGVjdChxdWVyeV9icCwgc2FtcGxlX2lkLCBiYXNlZnJlcXVlbmN5X3NkLCBhY3R1YWxfZ2VudXMsIGNvbmZpZGVuY2UpICU+JQogIG11dGF0ZShxdWVyeV9icCA9IDEwMDAqKHN0cl9yZW1vdmUocXVlcnlfYnAsICJLIikgJT4lIGFzLmludGVnZXIpKQoKZ2VudXNfcHJlZGljdGlvbnMgPSBnZW51c19wcmVkaWN0aW9ucyAlPiUKICBzZWxlY3Qoc2FtcGxlX2lkLCBhY3R1YWxfZ2VudXMpICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgZ3JvdXBfYnkoYWN0dWFsX2dlbnVzKSAlPiUKICBzdW1tYXJpc2UoTl9zYW1wbGVzID0gbigpKSAlPiUKICByaWdodF9qb2luKGdlbnVzX3ByZWRpY3Rpb25zKQoKZ2VudXNfcHJlZGljdGlvbnMgJT4lIGFycmFuZ2UoTl9zYW1wbGVzKQpgYGAKTm93IGxldCdzIG1ha2Ugc29tZSBwbG90cy4gRmlyc3QsIHdoYXQgaXMgdGhlIGVmZmVjdCBvZiBudW1iZXIgb2Ygc2FtcGxlcyBwZXIgY2xhc3MgaW4gY29uZmlkZW5jZT8KYGBge3J9CnNldC5zZWVkKDEzMjE0NTI2KQpwbG90X2dlbnVzX05fdnNfY29uZiA9IGdncGxvdChnZW51c19wcmVkaWN0aW9ucywgYWVzKHggPSBOX3NhbXBsZXMtMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBjb25maWRlbmNlKSkgKyAKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGE9MC4zKSArIAogIHNjYWxlX3hfbG9nMTAoKSArCiAgI3lsYWIoJ0NvbmZpZGVuY2UgaW4gY29ycmVjdCBwcmVkaWN0aW9uXG4obG9naXQgc2NhbGUpJykgKwogIHlsYWIoJ0NvbmZpZGVuY2UgaW4gY29ycmVjdCBnZW51cyBwcmVkaWN0aW9uJykgKwogIHhsYWIoJ051bWJlciBvZiB0cmFpbmluZyBzYW1wbGVzIGluIGNvcnJlY3QgZ2VudXNcbihsb2cgc2NhbGUpJykgKwogICNzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9naXQiLCBicmVha3MgPSBjKDFlLTQsMC4wMDEsMC4wMSwwLjEsMC4yNSwwLjUsMC43NSwwLjksMC45OSwwLjk5OSwxLTFlLTQpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMSkpICsKICB0aGVtZV9mZXcoKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC44KSkpCgpwbG90X2dlbnVzX05fdnNfY29uZgpgYGAKCk5vdywgd2hhdCBpcyB0aGUgZWZmZWN0IG9mIHNhbXBsZSBxdWFsaXR5IGluIGNvbmZpZGVuY2U/CmBgYHtyfQpzZXQuc2VlZCgxMzIxNDUyNikKcGxvdF9nZW51c19mcmVxc2RfdnNfY29uZiA9IGdncGxvdChnZW51c19wcmVkaWN0aW9ucywgYWVzKHggPSBiYXNlZnJlcXVlbmN5X3NkLCB5ID0gY29uZmlkZW5jZSkpICsgCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsgCiAgc2NhbGVfeF9sb2cxMCgpICsKICAjc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZ2l0IiwgYnJlYWtzID0gYygxZS00LDAuMDAxLDAuMDEsMC4xLDAuMjUsMC41LDAuNzUsMC45LDAuOTksMC45OTksMS0xZS00KSkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpKSArCiAgI3lsYWIoJ0NvbmZpZGVuY2UgaW4gY29ycmVjdCBwcmVkaWN0aW9uXG4obG9naXQgc2NhbGUpJykgKwogIHlsYWIoJ0NvbmZpZGVuY2UgaW4gY29ycmVjdCBnZW51cyBwcmVkaWN0aW9uJykgKwogIHhsYWIoJ1N0YW5kYXJkIGRldmlhdGlvbiBvZiBiYXNlIGZyZXF1ZW5jaWVzJykgKwogIHRoZW1lX2ZldygpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjgpKSkKCnBsb3RfZ2VudXNfZnJlcXNkX3ZzX2NvbmYKYGBgCgpOb3csIHdoYXQgaXMgdGhlIGVmZmVjdCBvZiBhbW91bnQgb2YgZGF0YSBpbiBjb25maWRlbmNlPwpgYGB7cn0Kc2V0LnNlZWQoMTMyMTQ1MjYpCnBsb3RfZ2VudXNfYnBfdnNfY29uZiA9IGdncGxvdChnZW51c19wcmVkaWN0aW9ucywgYWVzKHggPSBxdWVyeV9icCwgeSA9IGNvbmZpZGVuY2UpKSArIAogIGdlb21faml0dGVyKGFscGhhPTAuMykgKyAKICAjc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZ2l0IiwgYnJlYWtzID0gYygxZS00LDAuMDAxLDAuMDEsMC4xLDAuMjUsMC41LDAuNzUsMC45LDAuOTksMC45OTksMS0xZS00KSkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpKSArCiAgI3lsYWIoJ0NvbmZpZGVuY2UgaW4gY29ycmVjdCBwcmVkaWN0aW9uXG4obG9naXQgc2NhbGUpJykgKwogIHlsYWIoJ0NvbmZpZGVuY2UgaW4gY29ycmVjdCBnZW51cyBwcmVkaWN0aW9uJykgKwogIHhsYWIoJ0Jhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzXG4obG9nIHNjYWxlKScpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIHRoZW1lX2ZldygpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjgpKSkKCnBsb3RfZ2VudXNfYnBfdnNfY29uZgpgYGAKCkxldCdzIHB1dCBpdCBhbGwgdG9nZXRoZXIgbm93IGluIGEgbGluZWFyIG1vZGVsLiBXZSByZW1vdmUgb25lIGZyb20gTl9zYW1wbGVzIHNpbmNlIG9uZSBzYW1wbGUgaXMgdXNlZCBmb3IgdmFsaWRhdGlvbiBpbiB0aGUgbGVhdmUtb25lLW91dCBjcm9zcy12YWxpZGF0aW9uIHRoYXQgd2UgZGlkLgoKYGBge3J9CmxtX2RhdGEgPSBnZW51c19wcmVkaWN0aW9ucyAlPiUKICBtdXRhdGUoY29uZmlkZW5jZSA9IGlmZWxzZShjb25maWRlbmNlID09IDEsIGNvbmZpZGVuY2UtMC4wMDAwMDAxLCBjb25maWRlbmNlKSwKICAgICAgICAgY29uZmlkZW5jZSA9IGNhcjo6bG9naXQoY29uZmlkZW5jZSkpICU+JQogIG11dGF0ZShxdWVyeV9icCA9IChxdWVyeV9icCAtIG1lYW4ocXVlcnlfYnApKS9zZChxdWVyeV9icCksCiAgICAgICAgIGJhc2VmcmVxdWVuY3lfc2QgPSAoYmFzZWZyZXF1ZW5jeV9zZCAtIG1lYW4oYmFzZWZyZXF1ZW5jeV9zZCkpL3NkKGJhc2VmcmVxdWVuY3lfc2QpLAogICAgICAgICBOX3NhbXBsZXMgPSAoKE5fc2FtcGxlcy0xKSAtIG1lYW4oTl9zYW1wbGVzLTEpKS9zZChOX3NhbXBsZXMtMSkKICAgICAgICAgKSAKCmZ1bGxfbW9kZWwgPSBsbShmb3JtdWxhID0gY29uZmlkZW5jZX5xdWVyeV9icCpiYXNlZnJlcXVlbmN5X3NkKk5fc2FtcGxlcywgZGF0YSA9IGxtX2RhdGEpIApmdWxsX21vZGVsCnJlZHVjZWRfbW9kZWwgPSBzdGVwKGZ1bGxfbW9kZWwsZGlyZWN0aW9uID0gJ2JvdGgnKQpzdW1tYXJ5KHJlZHVjZWRfbW9kZWwpCnBsb3QocmVkdWNlZF9tb2RlbCkKCmJyb29tOjp0aWR5KHJlZHVjZWRfbW9kZWwpICU+JSB3cml0ZV9jc3YoJ2xtX3RhYmxlLmNzdicpCmBgYAoKTm93IGxldCdzIHNhdmUgdGhlIHRocmVlIG9mIHRoZW0gYXMgYSBzaW5nbGUgcGxvdCB1c2luZyBjb3dwbG90LgoKYGBge3J9CnByZWRzID0gZ2VudXNfcHJlZGljdGlvbnMgJT4lCiAgbXV0YXRlKE5fc2FtcGxlcz1OX3NhbXBsZXMtMSkgJT4lIAogIHNlbGVjdChvcmlnaW5hbF9OID0gTl9zYW1wbGVzLAogICAgICAgICBvcmlnaW5hbF9icCA9IHF1ZXJ5X2JwLCAKICAgICAgICAgb3JpZ2luYWxfc2QgPSBiYXNlZnJlcXVlbmN5X3NkKSAlPiUKICBtdXRhdGUocXVlcnlfYnAgPSAob3JpZ2luYWxfYnAgLSBtZWFuKG9yaWdpbmFsX2JwKSkvc2Qob3JpZ2luYWxfYnApLAogICAgICAgICBiYXNlZnJlcXVlbmN5X3NkID0gKG9yaWdpbmFsX3NkIC0gbWVhbihvcmlnaW5hbF9zZCkpL3NkKG9yaWdpbmFsX3NkKSwKICAgICAgICAgTl9zYW1wbGVzID0gKChvcmlnaW5hbF9OKSAtIG1lYW4ob3JpZ2luYWxfTikpL3NkKG9yaWdpbmFsX04pCiAgICAgICAgICkgCgpwcmVkcyA9IGJpbmRfcm93cyhleHBhbmQuZ3JpZChxdWVyeV9icD11bmlxdWUocHJlZHMkcXVlcnlfYnApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOX3NhbXBsZXM9MCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFzZWZyZXF1ZW5jeV9zZD0wCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgIGV4cGFuZC5ncmlkKHF1ZXJ5X2JwPTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5fc2FtcGxlcz11bmlxdWUocHJlZHMkTl9zYW1wbGVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFzZWZyZXF1ZW5jeV9zZD0wCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgIGV4cGFuZC5ncmlkKHF1ZXJ5X2JwPTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5fc2FtcGxlcz0wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXNlZnJlcXVlbmN5X3NkPXVuaXF1ZShwcmVkcyRiYXNlZnJlcXVlbmN5X3NkKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICkKCmxvZ2lzdGljIDwtIGZ1bmN0aW9uKEwpIHsKICAxIC8gKDEgKyBleHAoLUwpKQp9CgpwcmVkcyA9IHByZWRpY3QocmVkdWNlZF9tb2RlbCwgbmV3ZGF0YT1wcmVkcywgaW50ZXJ2YWw9J2NvbmYnKSAlPiUKICBhc190aWJibGUoKSAlPiUKICBtdXRhdGVfYWxsKH5sb2dpc3RpYygueCkrMC4wMDAwMDAxKSAlPiUKICByZW5hbWUoY29uZmlkZW5jZT1maXQpICU+JQogIGJpbmRfY29scyhwcmVkcyAlPiUKICAgICAgICAgICAgICBtdXRhdGUoTl9zYW1wbGVzID0gTl9zYW1wbGVzKnNkKGdlbnVzX3ByZWRpY3Rpb25zJE5fc2FtcGxlcy0xKSttZWFuKGdlbnVzX3ByZWRpY3Rpb25zJE5fc2FtcGxlcy0xKSwKICAgICAgICAgICAgICAgICAgICAgYmFzZWZyZXF1ZW5jeV9zZCA9IGJhc2VmcmVxdWVuY3lfc2Qqc2QoZ2VudXNfcHJlZGljdGlvbnMkYmFzZWZyZXF1ZW5jeV9zZCkrCiAgICAgICAgICAgICAgICAgICAgICAgbWVhbihnZW51c19wcmVkaWN0aW9ucyRiYXNlZnJlcXVlbmN5X3NkKSwKICAgICAgICAgICAgICAgICAgICAgcXVlcnlfYnAgPSBxdWVyeV9icCpzZChnZW51c19wcmVkaWN0aW9ucyRxdWVyeV9icCkrbWVhbihnZW51c19wcmVkaWN0aW9ucyRxdWVyeV9icCkpCiAgICAgICAgICAgICAgKQogIAoKYGBgCgoKYGBge3J9CmNvbWJpbmVkX2NvbmYgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9nZW51c19OX3ZzX2NvbmYgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fbGluZShkYXRhPShmaWx0ZXIocHJlZHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOX3NhbXBsZXMgIT0gbWVhbihnZW51c19wcmVkaWN0aW9ucyROX3NhbXBsZXMtMSkpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj0iYmx1ZSIpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fcmliYm9uKGRhdGE9KGZpbHRlcihwcmVkcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5fc2FtcGxlcyAhPSBtZWFuKGdlbnVzX3ByZWRpY3Rpb25zJE5fc2FtcGxlcy0xKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHltaW49bHdyLCB5bWF4PXVwciksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGw9ImxpZ2h0Ymx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhPTAuNCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF9nZW51c19icF92c19jb25mICsgdGhlbWUoYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9OCkpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fbGluZShkYXRhPShmaWx0ZXIocHJlZHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVyeV9icCAhPSBtZWFuKGdlbnVzX3ByZWRpY3Rpb25zJHF1ZXJ5X2JwKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPSJibHVlIikgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbV9yaWJib24oZGF0YT0oZmlsdGVyKHByZWRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlcnlfYnAgIT0gbWVhbihnZW51c19wcmVkaWN0aW9ucyRxdWVyeV9icCkpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh5bWluPWx3ciwgeW1heD11cHIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsPSJsaWdodGJsdWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYT0wLjQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RfZ2VudXNfZnJlcXNkX3ZzX2NvbmYgKyB0aGVtZShheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9OCkpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fbGluZShkYXRhPShmaWx0ZXIocHJlZHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXNlZnJlcXVlbmN5X3NkICE9IG1lYW4oZ2VudXNfcHJlZGljdGlvbnMkYmFzZWZyZXF1ZW5jeV9zZCkpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj0iYmx1ZSIpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fcmliYm9uKGRhdGE9KGZpbHRlcihwcmVkcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhc2VmcmVxdWVuY3lfc2QgIT0gbWVhbihnZW51c19wcmVkaWN0aW9ucyRiYXNlZnJlcXVlbmN5X3NkKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHltaW49bHdyLCB5bWF4PXVwciksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGw9ImxpZ2h0Ymx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhPTAuNCkpICsKICBwYXRjaHdvcms6OnBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ0ZhY3RvcnMgYWZmZWN0aW5nIHZhcktvZGUgcHJlZGljdGlvbiBhY2N1cmFjeScsdGhlbWUgPSB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PTAuNSkpKSAKCmNvbWJpbmVkX2NvbmYKCnNhZmVfZ2dzYXZlIChmaWxlbmFtZSA9ICdpbWFnZXNfbWFudXNjcmlwdC9zdXBwX2NvbmZfcHJlZGljdG9ycy5wZGYnLGRldmljZSA9ICdwZGYnLHdpZHRoID0gNyxoZWlnaHQ9Myx1bml0cyA9ICdpbicsdXNlRGluZ2JhdHM9RikKYGBgCgojIyBTa21lcgoKRm9yIHNrbWVyLCB3ZSBsZWZ0IGVhY2ggc2FtcGxlIG91dCwgYnVpbHQgYSByZWZlcmVuY2UgYW5kIHRoZW4gcXVlcmllZCB0aGF0IHNhbXBsZS4gV2UgaGF2ZSBzZXZlcmFsIGZpbGVzIGluIHdoaWNoIHJlZmVyZW5jZSBzYW1wbGVzIGFyZSBvcmRlcmVkIGJ5IHRoZWlyIGRpc3RhbmNlIHRvIHRoZSBxdWVyeSwgd2UgaGVyZSB3ZSB3aWxsIGV2YWx1YXRlIHdoZXRoZXIgdGhlIGNsb3Nlc3Qgc2FtcGxlIGlzIGZyb20gdGhlIGNvcnJlY3Qgc3BlY2llcyBvciBnZW51cy4KCkJlY2F1c2UgaXQgaXMgbm90IGNsZWFyIGhvdyBza21lciBiZWhhdmVzIGZvciBkaWZmZXJlbnQgbGV2ZWxzIG9mIGNvdmVyYWdlLCB3ZSByZXBlYXRlZCB0aGlzIGZvciBzZXZlcmFsIGlucHV0IHNpemVzIChpbiBudW1iZXIgb2YgYmFzZXBhaXJzKSBhcyBxdWVyeSwgYnV0IGFsd2F5cyB1c2VkIHRoZSBtYXhpbXVtIGlucHV0IGRpemUgYXZhaWxhYmxlICh1cCB0byAyMDBNYikgZm9yIHJlZmVyZW5jZXMuCgpMZXQncyBtYWtlIGEgZnVuY3Rpb24gdGhhdCBleHRyYWN0cyB0aGVzZSByZXN1bHRzIGFzIGEgdGFibGUuCgpgYGB7cn0KCnNhbXBfbGFiZWxzID0gcmVzdWx0cyAlPiUgc2VsZWN0KHNhbXBsZV9pZCxhY3R1YWxfbGFiZWxzKSAlPiUgZGlzdGluY3QoKQoKZXh0cmFjdF9za21lcl9yZXN1bHRzID0gZnVuY3Rpb24oZmlsZV9wYXRoKSB7CiAgICAjIFJlYWQgb25seSB0aGUgZmlyc3QgMiBsaW5lcyBvZiB0aGUgZmlsZQogICAgZmlsZV9saW5lcyA8LSByZWFkTGluZXMoZmlsZV9wYXRoLCBuID0gMikKICAgIAogICAgIyBFeHRyYWN0IHNhbXBsZV9JRCwgYmFzZXBhaXJzIGZyb20gdGhlIGZpcnN0IGxpbmUKICAgIHNhbXBsZV9pbmZvIDwtIHN0cl9tYXRjaChmaWxlX2xpbmVzWzFdLCAiXFxzKiguKj8pQChcXGQrSykiKVssIDI6M10KICAgIHNhbXBsZV9JRCA8LSBzYW1wbGVfaW5mb1sxXQogICAgYmFzZXBhaXJzIDwtIHNhbXBsZV9pbmZvWzJdCiAgICAKICAgICMgRXh0cmFjdCByZWZlcmVuY2Vfc2FtcGxlX0lELCBkaXN0YW5jZSBmcm9tIHRoZSBzZWNvbmQgbGluZQogICAgcmVmZXJlbmNlX2luZm8gPC0gc3RyX21hdGNoKGZpbGVfbGluZXNbMl0sICJcXHMqKC4qPylALipcXHMrKFxcZCtcXC5cXGQrKSIpWywgMjozXQogICAgcmVmZXJlbmNlX3NhbXBsZV9JRCA8LSByZWZlcmVuY2VfaW5mb1sxXQogICAgZGlzdGFuY2UgPC0gYXMubnVtZXJpYyhyZWZlcmVuY2VfaW5mb1syXSkKICAgIAogICAgIyBDcmVhdGUgYSB0aWJibGUKICAgIHRpYmJsZSgKICAgICAgICBzYW1wbGVfaWQgPSBzYW1wbGVfSUQsCiAgICAgICAgcXVlcnlfYnAgPSBiYXNlcGFpcnMsCiAgICAgICAgY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkID0gcmVmZXJlbmNlX3NhbXBsZV9JRCwKICAgICAgICBjbG9zZXN0X2Rpc3RhbmNlID0gZGlzdGFuY2UKICAgICkgCn0KYGBgCgpOb3cgd2Ugd2lsbCBhcHBseSB0aGlzIGZ1bmN0aW9uIHRvIGFsbCBza21lciBvdXRwdXQgZmlsZXMuCgpgYGB7cn0KcGxhbihtdWx0aXNlc3Npb24od29ya2VycyA9IDEyKSkKc2ttZXJfcmVzdWx0c19kZiA9IGZ1cnJyOjpmdXR1cmVfbWFwX2RmcigKICBsaXN0LmZpbGVzKCdNYWxwaWdoaWFsZXMvc2ttZXIvc2ttZXJfeHZhbF9yZXN1bHRzLycsIGZ1bGwubmFtZXMgPSBUKSwKICB+IGV4dHJhY3Rfc2ttZXJfcmVzdWx0cygueCkKKSAlPiUKICBsZWZ0X2pvaW4oc2FtcF9sYWJlbHMsIGJ5ID0gJ3NhbXBsZV9pZCcpICU+JQogIGxlZnRfam9pbigKICAgIHNhbXBfbGFiZWxzICU+JSBzZWxlY3QoCiAgICAgIGNsb3Nlc3RfcmVmZXJlbmNlX3NhbXBsZV9pZCA9ICdzYW1wbGVfaWQnLAogICAgICBwcmVkaWN0ZWRfbGFiZWxzID0gYWN0dWFsX2xhYmVscwogICAgKSwKICAgIGJ5ID0gJ2Nsb3Nlc3RfcmVmZXJlbmNlX3NhbXBsZV9pZCcKICApICU+JQogIG11dGF0ZSgKICAgIHF1ZXJ5X2xhYmVscyA9IHN0cl9yZW1vdmUoYWN0dWFsX2xhYmVscywgIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpLAogICAgcHJlZGljdGVkX2xpc3QgPSBzdHJfc3BsaXQocHJlZGljdGVkX2xhYmVscywgJzsnKQogICkgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZmFtaWx5JyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICBnZW51c19jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZ2VudXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgIHNwZWNpZXNfY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdCgKICAgICAgcXVlcnlfbGFiZWxzLCAnc3BlY2llcycKICAgICkpLAogICAgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnc3BlY2llcycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgTkEpLAogICAgZmFtaWx5X2luY29ycmVjdCA9IGFueSghKHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsICdmYW1pbHknKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdmYW1pbHknKV0pKSwKICAgIGdlbnVzX2luY29ycmVjdCA9IGFueSghKHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsICdnZW51cycpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2dlbnVzJyldKSksCiAgICBzcGVjaWVzX2luY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdCgKICAgICAgcXVlcnlfbGFiZWxzLCAnc3BlY2llcycKICAgICkpLAogICAgYW55KCEoCiAgICAgIHByZWRpY3RlZF9saXN0W3N0cl9kZXRlY3QocHJlZGljdGVkX2xpc3QsICdzcGVjaWVzJyldICVpbiUgcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnc3BlY2llcycpXQogICAgKSksCiAgICBOQSksCiAgICBxdWVyeV9icCA9IGFzLm51bWVyaWMoc3RyX3JlbW92ZShxdWVyeV9icCwgIksiKSkgKiAxMDAwCiAgICAKICApIApwbGFuKHNlcXVlbnRpYWwpCnNrbWVyX3Jlc3VsdHNfZGYKYGBgCk5vdyBsZXQncyBzdW1tYXJpemUgYW5kIHBsb3QgYnkgZ2VudXM6CgpgYGB7cn0Kc2ttZXJfc3VtbWFyeV9nZW51cyA9IHN1bW1hcml6ZV9yZXN1bHRzKHNrbWVyX3Jlc3VsdHNfZGYsJ2dlbnVzJykKcF9za21lcl9nZW51cyA9IHBsb3RfYXJlYShza21lcl9zdW1tYXJ5X2dlbnVzLCAnU2ttZXIgZ2VudXMnLCByZWxhdGl2ZSA9IFRSVUUpCnBfc2ttZXJfZ2VudXMKYGBgCk5vdyBieSBzcGVjaWVzLiBJbiBTa21lciwgdGhlcmUgaXMgbm8gaW5jb25jbHVzaXZlIHJlc3VsdDogaWYgdGhlcmUgaXMgbm8gY29ycmVjdCBzcGVjaWVzIHByZWRpY3Rpb24sIGl0IG1lYW5zIHRoYXQgYSBzYW1wbGUgd2FzIHByZWRpY3RlZCBpbiB0aGUgd3JvbmcgZ2VudXMgYW5kIHRoZXJlZm9yZSBpdCBpcyBpbmNvcnJlY3QKCmBgYHtyfQpza21lcl9zdW1tYXJ5X3NwZWNpZXMgPSBzdW1tYXJpemVfcmVzdWx0cyhza21lcl9yZXN1bHRzX2RmLCdzcGVjaWVzJykgJT4lCiAgbXV0YXRlKHJlc3VsdCA9IGlmZWxzZShyZXN1bHQgPT0gJ2NvcnJlY3QnLCAnY29ycmVjdCcsJ2luY29ycmVjdCcpKSAlPiUKICBncm91cF9ieShxdWVyeV9icCxyZXN1bHQpICU+JQogIHN1bW1hcmlzZV9hbGwoc3VtKQpwX3NrbWVyX3NwZWNpZXMgPSBwbG90X2FyZWEoc2ttZXJfc3VtbWFyeV9zcGVjaWVzLCAnU2ttZXIgc3BlY2llcycsIHJlbGF0aXZlID0gVFJVRSkKcF9za21lcl9zcGVjaWVzCmBgYAoKQW5kIG5vdyBieSBmYW1pbHk6CgpgYGB7cn0Kc2ttZXJfc3VtbWFyeV9mYW1pbHkgPSBzdW1tYXJpemVfcmVzdWx0cyhza21lcl9yZXN1bHRzX2RmLCdmYW1pbHknKQpza21lcl9zdW1tYXJ5X2ZhbWlseSAKcF9za21lcl9mYW1pbHkgPSBwbG90X2FyZWEoc2ttZXJfc3VtbWFyeV9mYW1pbHksICdTa21lciBmYW1pbHknLCByZWxhdGl2ZSA9IFRSVUUpCnBfc2ttZXJfZmFtaWx5CmBgYAoKIyBUcmFkaXRpb25hbCBiYXJjb2RlcwojIyBCTEFTVCBzaW5nbGUgZ2VuZQpMZXQncyBub3cgcmVhZCB0aGUgdHJhZGl0aW9uYWwgYmFyY29kZSBCTEFTVCByZXN1bHRzIGFuZCBzdW1tYXJpemUgdGhlbSBpbiB0aGUgc2FtZSB3YXkgYXMgc2ttZXIgYW5kIHZhcktvZGVyLiBMZXQncyBzdGFydCBieSBkZWZpbmluZyBhIGZ1Y3Rpb24gdGhhdCByZWFkcyB0aGUgZGF0YSBzbyB3ZSBjYW4gc3VtbWFyaXplIGl0IHVzaW5nIHRoZSBwcmV2aW91c2x5IGRlZmluZWQgZnVuY3Rpb25zLgoKYGBge3J9CnJlYWRfdHJhZGl0aW9uYWxfYmFyY29kZXMgPSBmdW5jdGlvbihicCkgewogIGlucHV0X2ZpbGUgPSBwYXN0ZTAoCiAgICAnTWFscGlnaGlhbGVzL3RyYWRpdGlvbmFsX2JhcmNvZGVzLzJfYmxhc3RfcGh5bG9nZW55X3Jlc3VsdC9HZW51cy8nLAogICAgYnAsCiAgICAnTV9ibGFzdF9waHlsb19zdW1fc3AudHN2JwogICkKICAKICBiYXJjb2RlX3JlcyA9IHJlYWRfZGVsaW0oaW5wdXRfZmlsZSkgJT4lCiAgICBwaXZvdF9sb25nZXIoLXNwLCBuYW1lc190byA9ICdtYXJrZXInLCB2YWx1ZXNfdG8gPSAnY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkJykgJT4lCiAgICByZW5hbWUoc2FtcGxlX2lkID0gJ3NwJykgJT4lCiAgICBtdXRhdGUoCiAgICAgIHNhbXBsZV9pZCA9IHN0cl9yZW1vdmVfYWxsKHNhbXBsZV9pZCwgJ0AuKycpLAogICAgICBjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQgPSBzdHJfcmVtb3ZlX2FsbChjbG9zZXN0X3JlZmVyZW5jZV9zYW1wbGVfaWQsICdALisnKSwKICAgICAgcHJlZGljdGVkX2xhYmVscyA9IHNhbXBfbGFiZWxzJGFjdHVhbF9sYWJlbHNbbWF0Y2goY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkLCBzYW1wX2xhYmVscyRzYW1wbGVfaWQpXSwKICAgICAgYWN0dWFsX2xhYmVscyA9IHNhbXBfbGFiZWxzJGFjdHVhbF9sYWJlbHNbbWF0Y2goc2FtcGxlX2lkLCBzYW1wX2xhYmVscyRzYW1wbGVfaWQpXQogICAgKSAlPiUKICAgIGZpbHRlcihtYXJrZXIgIT0gJ0NvbmNhdGVuYXRlZF9waHlsb2dlbnknKSAlPiUKICAgIG11dGF0ZSgKICAgICAgcXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCAiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JyksCiAgICAgIHByZWRpY3RlZF9saXN0ID0gc3RyX3NwbGl0KHByZWRpY3RlZF9sYWJlbHMsICc7JykKICAgICkgJT4lCiAgICByb3d3aXNlKCkgJT4lCiAgICBtdXRhdGUoCiAgICAgIGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZmFtaWx5JyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgIGdlbnVzX2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICBzcGVjaWVzX2NvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QoCiAgICAgICAgcXVlcnlfbGFiZWxzLCAnc3BlY2llcycKICAgICAgKSksCiAgICAgIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgTkEpLAogICAgICBmYW1pbHlfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ2ZhbWlseScpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2ZhbWlseScpXSkpLAogICAgICBnZW51c19pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnZ2VudXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSkpLAogICAgICBzcGVjaWVzX2luY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdCgKICAgICAgICBxdWVyeV9sYWJlbHMsICdzcGVjaWVzJwogICAgICApKSwKICAgICAgYW55KCEoCiAgICAgICAgcHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ3NwZWNpZXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdzcGVjaWVzJyldCiAgICAgICkpLAogICAgICBOQSkKICAgICkgJT4lCiAgICBtdXRhdGVfYXQodmFycyhlbmRzX3dpdGgoIl9jb3JyZWN0IiksIGVuZHNfd2l0aCgiX2luY29ycmVjdCIpKSwKICAgICAgICAgICAgICB+IGlmZWxzZShpcy5uYShwcmVkaWN0ZWRfbGFiZWxzKSAmICFpcy5uYSguKSwgRkFMU0UsIC4pKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGJwICogMWU2KQogIAogIHJldHVybihiYXJjb2RlX3JlcykKfQpgYGAKCgpOb3cgd2UgY2FuIGFwcGx5IHRoaXMgZnVuY3Rpb24gdG8gYWxsIG9mIG91ciByZXN1bHRzOgoKYGBge3J9CnJlc3VsdHNfYmFyY29kZXMgPSBwdXJycjo6bWFwX2RmcihjKDAuNSwxLDIsNSwxMCwyMCw1MCwxMDAsMjAwKSxyZWFkX3RyYWRpdGlvbmFsX2JhcmNvZGVzKQpyZXN1bHRzX2JhcmNvZGVzCmBgYAoKTm93IGxldCdzIHN1bW1hcmlzZSBmb3IgZWFjaCBtYXJrZXIgc2VwYXJhdGVseToKYGBge3J9CmJhcmNvZGVfc3VtbWFyeV9mYW1pbHkgPSBzcGxpdChyZXN1bHRzX2JhcmNvZGVzLHJlc3VsdHNfYmFyY29kZXMkbWFya2VyKSAlPiUKICBwdXJycjo6bWFwX2Rmcih+c3VtbWFyaXplX3Jlc3VsdHMoLngsJ2ZhbWlseScpLC5pZD0nbWFya2VyJykKCmJhcmNvZGVfc3VtbWFyeV9mYW1pbHkKYGBgCgpgYGB7cn0KYmFyY29kZV9zdW1tYXJ5X2dlbnVzID0gc3BsaXQocmVzdWx0c19iYXJjb2RlcyxyZXN1bHRzX2JhcmNvZGVzJG1hcmtlcikgJT4lCiAgcHVycnI6Om1hcF9kZnIofnN1bW1hcml6ZV9yZXN1bHRzKC54LCdnZW51cycpLC5pZD0nbWFya2VyJykKCmJhcmNvZGVfc3VtbWFyeV9nZW51cwpgYGAKCmBgYHtyfQpiYXJjb2RlX3N1bW1hcnlfc3BlY2llcyA9IHNwbGl0KHJlc3VsdHNfYmFyY29kZXMscmVzdWx0c19iYXJjb2RlcyRtYXJrZXIpICU+JQogIHB1cnJyOjptYXBfZGZyKH5zdW1tYXJpemVfcmVzdWx0cygueCwnc3BlY2llcycpLC5pZD0nbWFya2VyJykKCmJhcmNvZGVfc3VtbWFyeV9zcGVjaWVzCmBgYAoKTm93IGxldCdzIHBsb3QsIG1ha2luZyBzZXBhcmF0ZSBwbG90cyBmb3IgZWFjaCBtYXJrZXI6CgpTcGVjaWVzOgpgYGB7cn0KcF9iYXJjb2RlX3NwZWNpZXMgPSBiYXJjb2RlX3N1bW1hcnlfc3BlY2llcyAlPiUKICBzcGxpdChiYXJjb2RlX3N1bW1hcnlfc3BlY2llcyRtYXJrZXIpICU+JQogIHB1cnJyOjptYXAofnBsb3RfYXJlYSgueCxwYXN0ZTAodW5pcXVlKC54JG1hcmtlciksJyBzcGVjaWVzJyksIHJlbGF0aXZlID0gVFJVRSwgeGxpbV9hbGwgPSBUUlVFKSkKCnBfYmFyY29kZV9zcGVjaWVzCmBgYApHZW5lcmE6CmBgYHtyfQpwX2JhcmNvZGVfZ2VudXMgPSBiYXJjb2RlX3N1bW1hcnlfZ2VudXMgJT4lCiAgc3BsaXQoYmFyY29kZV9zdW1tYXJ5X2dlbnVzJG1hcmtlcikgJT4lCiAgcHVycnI6Om1hcCh+cGxvdF9hcmVhKC54LHBhc3RlMCh1bmlxdWUoLngkbWFya2VyKSwnIGdlbnVzJyksIHJlbGF0aXZlID0gVFJVRSwgeGxpbV9hbGwgPSBUUlVFKSkKCnBfYmFyY29kZV9nZW51cwpgYGAKRmFtaWx5OgpgYGB7cn0KcF9iYXJjb2RlX2ZhbWlseSA9IGJhcmNvZGVfc3VtbWFyeV9mYW1pbHkgJT4lCiAgc3BsaXQoYmFyY29kZV9zdW1tYXJ5X2ZhbWlseSRtYXJrZXIpICU+JQogIHB1cnJyOjptYXAofnBsb3RfYXJlYSgueCxwYXN0ZTAodW5pcXVlKC54JG1hcmtlciksJyBmYW1pbHknKSwgcmVsYXRpdmUgPSBUUlVFLHhsaW1fYWxsID0gVFJVRSkpCgpwX2JhcmNvZGVfZmFtaWx5CmBgYAoKIyMgQ29uY2F0ZW5hdGVkIHRyZWUKTm93IHdlIHdpbGwgZG8gdGhlIHNhbWUgZm9yIGNvbmNhdGVuYXRlZCB0cmVlLiBMZXQncyBzdGFydCBieSBkZWZpbmluZyBhIGZ1bmN0aW9uIHRvIGdhdGhlciByZXN1bHRzLiBXZSB3aWxsIGNvbnNpZGVyIGEgcmVzdWx0IGFzIGNvcnJlY3QgaWYgdGhlIG1ham9yaXR5IG9mIHRoZSBzaXN0ZXIgdGF4b24gdG8gYSB0aXAgaGFzIHRoZSBzYW1lIGxhYmVsLgoKCmBgYHtyfQoKcmVhZF9jb25jYXRlbmF0ZWRfdHJlZV9yZXN1bHRzID0gZnVuY3Rpb24oYnApewogIAogIAojIFJlYWQgaW4geW91ciB0cmVlIC0gcmVwbGFjZSAneW91cl90cmVlX2ZpbGUubndrJyB3aXRoIHRoZSBwYXRoIHRvIHlvdXIgdHJlZSBmaWxlCnRyZWUgPSByZWFkLnRyZWUocGFzdGUwKCdNYWxwaWdoaWFsZXMvdHJhZGl0aW9uYWxfYmFyY29kZXMvMl9ibGFzdF9waHlsb2dlbnlfcmVzdWx0L0dlbnVzL2NvbmMuJyxicCwnbS5zcG5hbWUudHJlJykpCgojbGVhdmUgb25seSBzYW1wbGUgSURzIGFzIHRpcCBsYWJlbHMKdHJlZSR0aXAubGFiZWwgPSB0cmVlJHRpcC5sYWJlbCAlPiUgc3RyX3JlbW92ZSgiLipAIikgJT4lIHN0cl9yZW1vdmUoIiciKSAlPiUgc3RyX3JlcGxhY2UoJyByZWYnLCdfcmVmJykKCiMgQ29tcHV0ZSB0aGUgcGF0cmlzdGljIGRpc3RhbmNlcyBhbmQgbGlzdCBhbGwgcmVmZXJlbmNlIG5hbWVzCnBhdHJpc3RpY19kaXN0YW5jZXMgPC0gY29waGVuZXRpYyh0cmVlKQphbGxfcmVmX25hbWVzID0gZGltbmFtZXMocGF0cmlzdGljX2Rpc3RhbmNlcylbWzFdXVtzdHJfZGV0ZWN0KGRpbW5hbWVzKHBhdHJpc3RpY19kaXN0YW5jZXMpW1sxXV0sJ19yZWYkJyldCmFsbF9ub25yZWYgPSBkaW1uYW1lcyhwYXRyaXN0aWNfZGlzdGFuY2VzKVtbMV1dW3N0cl9kZXRlY3QoZGltbmFtZXMocGF0cmlzdGljX2Rpc3RhbmNlcylbWzFdXSwnX3JlZiQnLG5lZ2F0ZSA9IFRSVUUpXQoKIyBGb3IgZWFjaCB0aXAsIGZpbmQgdGhlIHJlZmVyZW5jZSBzYW1wbGUgd2l0aCBjbG9zZXN0IHBhdHJpc3RpYyBkaXN0YW5jZQpmaW5kX2Nsb3Nlc3QgPSBmdW5jdGlvbih0aXApewogIHRvX2tlZXAgPSBjKHRpcCxhbGxfcmVmX25hbWVzW3N0cl9kZXRlY3QoYWxsX3JlZl9uYW1lcyxwYXN0ZTAodGlwLCdfcmVmJyksbmVnYXRlID0gVFJVRSldKQogIHJldHVybihuYW1lcyhzb3J0KHBhdHJpc3RpY19kaXN0YW5jZXNbdGlwLHRvX2tlZXBdKVsyXSkgJT4lCiAgICAgICAgICAgc3RyX3JlbW92ZSgnX3JlZicpKQp9CgpjbG9zZXN0X21hdGNoID0gcHVycnI6Om1hcF9jaHIoYWxsX25vbnJlZixmaW5kX2Nsb3Nlc3QpCgpzYW1wbGVzX3dpdGhfZGF0YSA9IHJlYWRfZGVsaW0ocGFzdGUwKCdNYWxwaWdoaWFsZXMvdHJhZGl0aW9uYWxfYmFyY29kZXMvMl9ibGFzdF9waHlsb2dlbnlfcmVzdWx0L0dlbnVzLycsYnAsJ01fYmxhc3RfcGh5bG9fc3VtX3NwLnRzdicpKSAlPiUgCiAgc2VsZWN0KHNhbXBsZV9pZD1zcCkgJT4lCiAgbXV0YXRlKHNhbXBsZV9pZCA9IHN0cl9yZW1vdmVfYWxsKHNhbXBsZV9pZCwgJ0AuKycpKQoKYmFyY29kZV9yZXMgPSB0aWJibGUoc2FtcGxlX2lkID0gYWxsX25vbnJlZiwKICAgICAgIGNsb3Nlc3RfcmVmZXJlbmNlX3NhbXBsZV9pZCA9IGNsb3Nlc3RfbWF0Y2gpICU+JQogIHJpZ2h0X2pvaW4oc2FtcGxlc193aXRoX2RhdGEpICU+JQogIG11dGF0ZSgKICAgICAgcHJlZGljdGVkX2xhYmVscyA9IHNhbXBfbGFiZWxzJGFjdHVhbF9sYWJlbHNbbWF0Y2goY2xvc2VzdF9yZWZlcmVuY2Vfc2FtcGxlX2lkLCBzYW1wX2xhYmVscyRzYW1wbGVfaWQpXSwKICAgICAgYWN0dWFsX2xhYmVscyA9IHNhbXBfbGFiZWxzJGFjdHVhbF9sYWJlbHNbbWF0Y2goc2FtcGxlX2lkLCBzYW1wX2xhYmVscyRzYW1wbGVfaWQpXQogICAgKSAlPiUKICBmaWx0ZXIoc2FtcGxlX2lkIT0nMjA5NScpICU+JQogIG11dGF0ZSgKICAgICAgcXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCAiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JyksCiAgICAgIHByZWRpY3RlZF9saXN0ID0gc3RyX3NwbGl0KHByZWRpY3RlZF9sYWJlbHMsICc7JykKICAgICkgJT4lCiAgICByb3d3aXNlKCkgJT4lCiAgICBtdXRhdGUoCiAgICAgIGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzW3N0cl9kZXRlY3QocXVlcnlfbGFiZWxzLCAnZmFtaWx5JyldICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgIGdlbnVzX2NvcnJlY3QgPSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICBzcGVjaWVzX2NvcnJlY3QgPSBpZmVsc2UoYW55KHN0cl9kZXRlY3QoCiAgICAgICAgcXVlcnlfbGFiZWxzLCAnc3BlY2llcycKICAgICAgKSksCiAgICAgIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ3NwZWNpZXMnKV0gJWluJSBwcmVkaWN0ZWRfbGlzdCwKICAgICAgTkEpLAogICAgICBmYW1pbHlfaW5jb3JyZWN0ID0gYW55KCEocHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ2ZhbWlseScpXSAlaW4lIHF1ZXJ5X2xhYmVsc1tzdHJfZGV0ZWN0KHF1ZXJ5X2xhYmVscywgJ2ZhbWlseScpXSkpLAogICAgICBnZW51c19pbmNvcnJlY3QgPSBhbnkoIShwcmVkaWN0ZWRfbGlzdFtzdHJfZGV0ZWN0KHByZWRpY3RlZF9saXN0LCAnZ2VudXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdnZW51cycpXSkpLAogICAgICBzcGVjaWVzX2luY29ycmVjdCA9IGlmZWxzZShhbnkoc3RyX2RldGVjdCgKICAgICAgICBxdWVyeV9sYWJlbHMsICdzcGVjaWVzJwogICAgICApKSwKICAgICAgYW55KCEoCiAgICAgICAgcHJlZGljdGVkX2xpc3Rbc3RyX2RldGVjdChwcmVkaWN0ZWRfbGlzdCwgJ3NwZWNpZXMnKV0gJWluJSBxdWVyeV9sYWJlbHNbc3RyX2RldGVjdChxdWVyeV9sYWJlbHMsICdzcGVjaWVzJyldCiAgICAgICkpLAogICAgICBOQSkKICAgICkgJT4lCiAgICBtdXRhdGVfYXQodmFycyhlbmRzX3dpdGgoIl9jb3JyZWN0IiksIGVuZHNfd2l0aCgiX2luY29ycmVjdCIpKSwKICAgICAgICAgICAgICB+IGlmZWxzZShpcy5uYShwcmVkaWN0ZWRfbGFiZWxzKSAmICFpcy5uYSguKSwgRkFMU0UsIC4pKSAlPiUKICAgIG11dGF0ZShxdWVyeV9icCA9IGJwICogMWU2KQogIAogIHJldHVybihiYXJjb2RlX3JlcykKfQoKYGBgCgpOb3cgbGV0J3MgYXBwbHkgdGhpcyBmdW5jdGlvbgpgYGB7cn0KcmVzdWx0c19jb25jYXRfYmFyY29kZXMgPSBwdXJycjo6bWFwX2RmcihjKDAuNSwxLDIsNSwxMCwyMCw1MCwxMDAsMjAwKSxyZWFkX2NvbmNhdGVuYXRlZF90cmVlX3Jlc3VsdHMpCnJlc3VsdHNfY29uY2F0X2JhcmNvZGVzCmBgYApMZXQncyBzdW1tYXJpemUgcmVzdWx0cyBhbmQgcGxvdCBmb3IgZ2VudXMsIHNwZWNpZXMgYW5kIGZhbWlseSBhY2N1cmFjeQoKYGBge3J9CmNvbmNhdF9zdW1tYXJ5X3NwZWNpZXMgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzX2NvbmNhdF9iYXJjb2Rlcywnc3BlY2llcycpCnBfY29uY2F0X3NwZWNpZXMgPSBwbG90X2FyZWEoY29uY2F0X3N1bW1hcnlfc3BlY2llcywgcmVsYXRpdmUgPSBGQUxTRSx0aXRsZSA9ICdDb25jYXRlbmF0ZWQgYmFyY29kZXMgc3BlY2llcycseGxpbV9hbGwgPSBUUlVFKQpwX2NvbmNhdF9zcGVjaWVzCmBgYApgYGB7cn0KY29uY2F0X3N1bW1hcnlfZ2VudXMgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzX2NvbmNhdF9iYXJjb2RlcywnZ2VudXMnKQpwX2NvbmNhdF9nZW51cyA9IHBsb3RfYXJlYShjb25jYXRfc3VtbWFyeV9nZW51cywgcmVsYXRpdmUgPSBUUlVFLHRpdGxlID0gJ0NvbmNhdGVuYXRlZCBiYXJjb2RlcyBnZW51cycseGxpbV9hbGwgPSBUUlVFKQpwX2NvbmNhdF9nZW51cwpgYGAKCmBgYHtyfQpjb25jYXRfc3VtbWFyeV9mYW1pbHkgPSBzdW1tYXJpemVfcmVzdWx0cyhyZXN1bHRzX2NvbmNhdF9iYXJjb2RlcywnZmFtaWx5JykKcF9jb25jYXRfZmFtaWx5ID0gcGxvdF9hcmVhKGNvbmNhdF9zdW1tYXJ5X2ZhbWlseSwgcmVsYXRpdmUgPSBUUlVFLHRpdGxlID0gJ0NvbmNhdGVuYXRlZCBiYXJjb2RlcyBmYW1pbHknLHhsaW1fYWxsID0gVFJVRSkKcF9jb25jYXRfZmFtaWx5CmBgYAoKCiMgRGlyZWN0IGNvbXBhcmlzb24KCk5vdyBsZXQncyBjb21wYXJlIG1ldGhvZHMgc2lkZSBieSBzaWRlLiBGb3IgZ2VudXMgbGV2ZWw6CmBgYHtyIGZpZy5oZWlnaHQ9MTB9CnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocF9nZW51cyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwgCiAgICAgICAgICAgICAgICAgICBwX3NrbWVyX2dlbnVzICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpLAogICAgICAgICAgICAgICAgICAgcF9iYXJjb2RlX2dlbnVzJElUUyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9nZW51cyRyYmNMICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpLAogICAgICAgICAgICAgICAgICAgcF9jb25jYXRfZ2VudXMsCiAgICAgICAgICAgICAgICAgICBuY29sID0gMSkgKwogIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICdHZW51cy1sZXZlbCBhY2N1cmFjeScpCnAKc2FmZV9nZ3NhdmUgKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX2dlbnVzX2FjY3VyYWN5LnBkZicsIHdpZHRoPTUsaGVpZ2h0ID0gMTApCnNhZmVfZ2dzYXZlICgnaW1hZ2VzX21hbnVzY3JpcHQvZmlnM19nZW51c19hY2N1cmFjeS5wbmcnLCB3aWR0aD01LGhlaWdodCA9IDEwLGRwaT0xMjAwKQpgYGAKTm93IGZvciBzcGVjaWVzIGxldmVsOgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTB9CnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocF9zcGVjaWVzICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpLCAKICAgICAgICAgICAgICAgICAgIHBfc2ttZXJfc3BlY2llcyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9zcGVjaWVzJElUUyArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9zcGVjaWVzJHJiY0wgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSksCiAgICAgICAgICAgICAgICAgICBwX2NvbmNhdF9zcGVjaWVzLAogICAgICAgICAgICAgICAgICAgbmNvbCA9IDEpICsKICBwbG90X2Fubm90YXRpb24odGl0bGUgPSAnc3BlY2llcy1sZXZlbCBhY2N1cmFjeScpCnAKc2FmZV9nZ3NhdmUgKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX3NwZWNpZXNfYWNjdXJhY3kucGRmJywgd2lkdGg9NSxoZWlnaHQgPSAxMCkKc2FmZV9nZ3NhdmUgKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX3NwZWNpZXNfYWNjdXJhY3kucG5nJywgd2lkdGg9NSxoZWlnaHQgPSAxMCxkcGk9MTIwMCkKYGBgCk5vdyBmb3IgZmFtaWx5IGxldmVsOgpgYGB7ciBmaWcuaGVpZ2h0ID0gMTB9CnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocF9mYW1pbHkgKyBnZ3RpdGxlKCd2YXJLb2RlcicpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpLCAKICAgICAgICAgICAgICAgICAgIHBfc2ttZXJfZmFtaWx5ICsgZ2d0aXRsZSgnU2ttZXInKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpLAogICAgICAgICAgICAgICAgICAgcF9iYXJjb2RlX2ZhbWlseSRJVFMgKyBnZ3RpdGxlKCdJVFMnKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9mYW1pbHkkcmJjTCArIGdndGl0bGUoJ3JiY0wnKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKSwKICAgICAgICAgICAgICAgICAgIHBfY29uY2F0X2ZhbWlseSArIGdndGl0bGUoJ0NvbmNhdGVuYXRlZCBjb252ZW50aW9uYWwgYmFyY29kZXMnKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJyksCiAgICAgICAgICAgICAgICAgICBuY29sID0gMSxndWlkZXMgPSAnY29sbGVjdCcpICsKICBwbG90X2Fubm90YXRpb24odGl0bGUgPSAnRmFtaWx5LWxldmVsIGFjY3VyYWN5Jyx0aGVtZSA9IHRoZW1lKHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KGhqdXN0PTAuNSkpKQpwCnNhZmVfZ2dzYXZlICgnaW1hZ2VzX21hbnVzY3JpcHQvZmlnM19mYW1pbHlfYWNjdXJhY3kucGRmJywgd2lkdGg9NSxoZWlnaHQgPSAxMCkKc2FmZV9nZ3NhdmUgKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX2ZhbWlseV9hY2N1cmFjeS5wbmcnLCB3aWR0aD01LGhlaWdodCA9IDEwLGRwaT0xMjAwKQpgYGAKCk5vdyBsZXQncyBwbG90IGEgZmlndXJlIGZvciB0aGUgb3RoZXIgdHJhZGl0aW9uYWwgYmFyY29kZSBsb2NpIHRoYXQgZGlkIG5vdCBtYWtlIHRoaXMgbGlzdC4KCmBgYHtyfQpwMSA9IHBhdGNod29yazo6d3JhcF9wbG90cyhnZ3Bsb3QoKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdndGl0bGUoIlNwZWNpZXMiKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9MC41KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLDAsMCwwKSwnbGluZXMnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9zcGVjaWVzJG1hdEsgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3RpdGxlKCdtYXRLJykgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnkgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcF9iYXJjb2RlX3NwZWNpZXMkbmRoRiArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2d0aXRsZSgnbmRoRicpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9zcGVjaWVzJGB0cm5MLUZgICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3RpdGxlKCd0cm5MLUYnKSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUoYXhpcy50aXRsZS55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9ZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIG5jb2wgPSAxLAogICAgICAgICAgICAgICAgICBoZWlnaHRzID0gYygxLDE1LDE1LDE1KQogICAgICAgICAgICAgICAgICApIAoKCnAyID0gcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKGdncGxvdCgpICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWVfbWluaW1hbCgpICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2d0aXRsZSgiR2VudXMiKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9MC41KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLDAsMCwwKSwnbGluZXMnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9nZW51cyRtYXRLICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2d0aXRsZSgnbWF0SycpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9nZW51cyRuZGhGICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3RpdGxlKCduZGhGJykgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnkgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcF9iYXJjb2RlX2dlbnVzJGB0cm5MLUZgICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3RpdGxlKCd0cm5MLUYnKSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUoYXhpcy50aXRsZS55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9ZWxlbWVudF9ibGFuaygpKSwKICAgICAgICAgICAgICAgICAgIG5jb2wgPSAxLAogICAgICAgICAgICAgICAgICBoZWlnaHRzID0gYygxLDE1LDE1LDE1KSkgCgpwMyA9IHBhdGNod29yazo6d3JhcF9wbG90cyhnZ3Bsb3QoKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdndGl0bGUoIkZhbWlseSIpICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3QubWFyZ2luID0gdW5pdChjKDAsMCwwLDApLCdsaW5lcycpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcF9iYXJjb2RlX2ZhbWlseSRtYXRLICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2d0aXRsZSgnbWF0SycpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9mYW1pbHkkbmRoRiArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2d0aXRsZSgnbmRoRicpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBfYmFyY29kZV9mYW1pbHkkYHRybkwtRmAgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdndGl0bGUoJ3RybkwtRicpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZShheGlzLnRpdGxlLnkgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPWVsZW1lbnRfYmxhbmsoKSksCiAgICAgICAgICAgICAgICAgICBuY29sID0gMSwKICAgICAgICAgICAgICAgICAgIGhlaWdodHMgPSBjKDEsMTUsMTUsMTUpLAogICAgICAgICAgICAgICAgICAgZ3VpZGVzPSdjb2xsZWN0JykKCnAgPSBwYXRjaHdvcms6OndyYXBfcGxvdHMocDEscDIscDMsbmNvbD0zLGd1aWRlcz0iY29sbGVjdCIpICsKICAgcGxvdF9hbm5vdGF0aW9uKAogICAgdGl0bGUgPSAnQ29udmVudGlvbmFsIGJhcmNvZGUgYWNjdXJhY3kgYWNyb3NzIGRpZmZlcmVudCB0YXhvbm9taWMgbGV2ZWxzJywKICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuMixmYWNlPSdib2xkJyxzaXplPTE1KSkKICApCgpzYWZlX2dnc2F2ZSAoJ2ltYWdlc19tYW51c2NyaXB0L3N1cHBfdHJhZGl0aW9uYWxfYmFyY29kZXMucGRmJywgcGxvdCA9IHAsIHdpZHRoPTgsaGVpZ2h0ID0gNikKYGBgCgoKCiMgU1JBOiBldWthcnlvdGljIGZhbWlsaWVzCgojIyB2YXJLb2RlcwoKRmluYWxseSwgbGV0J3Mgc3VtbWFyaXplIHJlc3VsdHMgZm9yIHRoZSB3aG9sZSBTUkEgZGF0YXNldC4gSW4gdGhpcyBjYXNlLCB3ZSBvbmx5IGhhdmUgdmFyS29kZXIgc2luY2UgU2ttZXIgY2Fubm90IGZpbmlzaCBhbmQgdHJhZGl0aW9uYWwgYmFyY29kZXMgYXJlIGluYXBwbGljYWJsZS4gV2UgaGF2ZSBkb25lIHByZWRpY3Rpb25zIHNlcGFyYXRlbHkgZm9yIGZhbWlsaWVzIGluY2x1ZGVkIGluIHRoZSB0cmFpbmluZyBzZXQgYW5kIGZhbWlsaWVzIG5vdCBpbmNsdWRlZCBpbiB0aGUgdHJhaW5pbmdzIHNldCwgc28gd2Ugd2lsbCBsb2FkIGVhY2ggb25lIGFuZCBjb25jYXRlbmF0ZQoKYGBge3J9CnZhcktvZGVyX1NSQV9yZXN1bHRzICA9IHJlYWRfY3N2KCdhbGxfU1JBX2V1a2FyeW90ZV9mYW1pbGllcy92a2ZDR1JfcXVlcnlfcmVzdWx0cy9wcmVkaWN0aW9ucy5jc3YnKSAlPiUKc2VsZWN0KC0xKSAlPiUKICBtdXRhdGUocXVlcnlfYmFzZXBhaXJzID0gY2FzZV93aGVuKAogICAgaXMuY2hhcmFjdGVyKHF1ZXJ5X2Jhc2VwYWlycykgfiBhcy5udW1lcmljKHN0cl9yZW1vdmUocXVlcnlfYmFzZXBhaXJzLCAiSyIpKSAqIDEwMDAsCiAgICBUUlVFIH4gYXMubnVtZXJpYyhxdWVyeV9iYXNlcGFpcnMpCiAgKSkgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QoZm9ybWF0KHF1ZXJ5X2Jhc2VwYWlycywgc2NpZW50aWZpYyA9IEZBTFNFLHRyaW0gPSBUUlVFKSwgIl5bMTI1XTArJCIpKSAlPiUgI3dlIHdpbGwgaWdub3JlIHF1ZXJpZXMgdGhhdCBhcmUgbm90IHN0YW5kYXJkaXplZCBzaXplcwogIHJlbmFtZShxdWVyeV9icCA9IHF1ZXJ5X2Jhc2VwYWlycykgJT4lCiAgbXV0YXRlKHF1YWxpdHlfaW5jbHVkZWQgPSBUKQpwbGFuKHNlcXVlbnRpYWwpCgoKdmFyS29kZXJfU1JBX3Jlc3VsdHMgPSB2YXJLb2Rlcl9TUkFfcmVzdWx0cyAlPiUKICBtdXRhdGUocXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSAlPiUgdW5saXN0LAogICAgICAgICBwcmVkaWN0ZWRfbGlzdCA9IHN0cl9zcGxpdChwcmVkaWN0ZWRfbGFiZWxzLCc7JykKICAgICAgICAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgICAgIGZhbWlseV9pbmNvcnJlY3QgPSBpZmVsc2UoaXMubmEocHJlZGljdGVkX2xhYmVscyksRkFMU0UsYW55KCEocHJlZGljdGVkX2xpc3QgJWluJSBxdWVyeV9sYWJlbHMpKSksCiAgICAgICAgIGZhbWlseV9pbl90cmFpbmluZyA9IFRSVUUpICU+JQogc2VsZWN0KG1hdGNoZXMoIl5bXjAtOV0iKSkKCnZhcktvZGVyX1NSQV9yZXN1bHRzIAogICAgICAgICAKYGBgCgpgYGB7cn0KdmFyS29kZXJfU1JBX3Jlc3VsdHNfbm90aW5jbHVkZWQgID0gcmVhZF9jc3YoJ2FsbF9TUkFfZXVrYXJ5b3RlX2ZhbWlsaWVzL3ZrZkNHUl9xdWVyeV9ub3RpbmNsdWRlZF9yZXN1bHRzL3ByZWRpY3Rpb25zLmNzdicpICU+JQpzZWxlY3QoLTEpICU+JQogIG11dGF0ZShxdWVyeV9iYXNlcGFpcnMgPSBjYXNlX3doZW4oCiAgICBpcy5jaGFyYWN0ZXIocXVlcnlfYmFzZXBhaXJzKSB+IGFzLm51bWVyaWMoc3RyX3JlbW92ZShxdWVyeV9iYXNlcGFpcnMsICJLIikpICogMTAwMCwKICAgIFRSVUUgfiBhcy5udW1lcmljKHF1ZXJ5X2Jhc2VwYWlycykKICApKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChmb3JtYXQocXVlcnlfYmFzZXBhaXJzLCBzY2llbnRpZmljID0gRkFMU0UsdHJpbSA9IFRSVUUpLCAiXlsxMjVdMCskIikpICU+JSAjd2Ugd2lsbCBpZ25vcmUgcXVlcmllcyB0aGF0IGFyZSBub3Qgc3RhbmRhcmRpemVkIHNpemVzCiAgcmVuYW1lKHF1ZXJ5X2JwID0gcXVlcnlfYmFzZXBhaXJzKSAlPiUKICBtdXRhdGUocXVhbGl0eV9pbmNsdWRlZCA9IFQpCnBsYW4oc2VxdWVudGlhbCkKClNSQV90YXhsYWJlbHNfbm90aW5jbHVkZWQgPSBzdHJfcmVtb3ZlKHZhcktvZGVyX1NSQV9yZXN1bHRzX25vdGluY2x1ZGVkJGFjdHVhbF9sYWJlbHMsIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpICU+JSB1bmxpc3QgJT4lIHVuaXF1ZQoKdmFyS29kZXJfU1JBX3Jlc3VsdHNfbm90aW5jbHVkZWQgPSB2YXJLb2Rlcl9TUkFfcmVzdWx0c19ub3RpbmNsdWRlZCAlPiUKICBtdXRhdGUocXVlcnlfbGFiZWxzID0gc3RyX3JlbW92ZShhY3R1YWxfbGFiZWxzLCI7Kmxvd19xdWFsaXR5OlRydWU7KiIpICU+JSBzdHJfc3BsaXQoJzsnKSAlPiUgdW5saXN0LAogICAgICAgICBwcmVkaWN0ZWRfbGlzdCA9IHN0cl9zcGxpdChwcmVkaWN0ZWRfbGFiZWxzLCc7JykKICAgICAgICAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgICAgIGZhbWlseV9pbmNvcnJlY3QgPSBpZmVsc2UoaXMubmEocHJlZGljdGVkX2xhYmVscyksRkFMU0UsYW55KCEocHJlZGljdGVkX2xpc3QgJWluJSBxdWVyeV9sYWJlbHMpKSksCiAgICAgICAgIGZhbWlseV9pbl90cmFpbmluZyA9IEZBTFNFKSAlPiUKIHNlbGVjdChtYXRjaGVzKCJeW14wLTldIikpCgp2YXJLb2Rlcl9TUkFfcmVzdWx0c19ub3RpbmNsdWRlZCAKYGBgCgpOb3cgbGV0J3Mgc3VtbWFyaXplIGFuZCBwbG90OgoKYGBge3J9ClNSQV9zdW1tYXJ5X2ZhbWlseSA9IGJpbmRfcm93cyhzdW1tYXJpemVfcmVzdWx0cyh2YXJLb2Rlcl9TUkFfcmVzdWx0cywnZmFtaWx5JykgJT4lIG11dGF0ZShmYW1pbHlfaW5fdHJhaW5pbmcgPSBUUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcml6ZV9yZXN1bHRzKHZhcktvZGVyX1NSQV9yZXN1bHRzX25vdGluY2x1ZGVkLCdmYW1pbHknKSAlPiUgbXV0YXRlKGZhbWlseV9pbl90cmFpbmluZyA9IEZBTFNFKSkKU1JBX3N1bW1hcnlfZmFtaWx5CgpOX3NhbXAgPSBTUkFfc3VtbWFyeV9mYW1pbHkgJT4lCiBncm91cF9ieShxdWVyeV9icCwgZmFtaWx5X2luX3RyYWluaW5nKSAlPiUKIHN1bW1hcmlzZShOID0gc3VtKE4pKQoKcF9TUkFfZmFtaWx5ID0gcGxvdF9hcmVhKFNSQV9zdW1tYXJ5X2ZhbWlseSwgJ3ZhcktvZGVyIFNSQSBmYW1pbHknLCByZWxhdGl2ZSA9IFRSVUUseGxpbV9hbGwgPSBGQUxTRSwgd3JhcCA9ICd+ZmFtaWx5X2luX3RyYWluaW5nJykKcF9TUkFfZmFtaWx5IApgYGAKCkxldCdzIG5vdyBkbyB0aGUgU1JBIHBsb3QsIGJ1dCBzcGxpdHRpbmcgYnkga2luZ2RvbSBhbmQgd2hldGhlciBvciBub3QgZmFtaWx5IHdhcyBpbmNsdWRlZCBpbiB0cmFpbmluZy4gRmlyc3QsIHdlIG5lZWQgdG8gcmV0cmlldmUga2luZ2RvbSBpbmZvcm1hdGlvbjoKYGBge3J9CgpzdW1tYXJ5X1NSQV9ieV9raW5nZG9tID0gcmVhZF9jc3YoJ2FsbF9TUkFfZXVrYXJ5b3RlX2ZhbWlsaWVzL3J1bnNfdG9fZG93bmxvYWRfZGF0YS5jc3YnKSAlPiUKICBzZWxlY3Qoc2FtcGxlX2lkID0gUnVuLCBLaW5nZG9tKSAlPiUKICByaWdodF9qb2luKHZhcktvZGVyX1NSQV9yZXN1bHRzKSAlPiUKICBzcGxpdCguJEtpbmdkb20pICU+JQogIHB1cnJyOjptYXBfZGYoc3VtbWFyaXplX3Jlc3VsdHMsIAogICAgICAgICAgICAgICAgIGxldmVsPSdmYW1pbHknLAogICAgICAgICAgICAgICAgLmlkPSdLaW5nZG9tJwogICAgICAgICAgICAgICAgKSAlPiUKICBtdXRhdGUoS2luZ2RvbSA9IGZhY3RvcihLaW5nZG9tLGxldmVscz1jKCdNZXRhem9hJywnVmlyaWRpcGxhbnRhZScsJ0Z1bmdpJyksb3JkZXJlZCA9IFQpLAogICAgICAgICBmYW1pbHlfaW5fdHJhaW5pbmcgPSBUKSAlPiUKICBiaW5kX3Jvd3MocmVhZF9jc3YoJ2FsbF9TUkFfZXVrYXJ5b3RlX2ZhbWlsaWVzL3J1bnNfbm90aW5jbHVkZWRfdG9fZG93bmxvYWRfZGF0YS5jc3YnKSAlPiUKICBzZWxlY3Qoc2FtcGxlX2lkID0gUnVuLCBLaW5nZG9tKSAlPiUKICByaWdodF9qb2luKHZhcktvZGVyX1NSQV9yZXN1bHRzX25vdGluY2x1ZGVkKSAlPiUKICBzcGxpdCguJEtpbmdkb20pICU+JQogIHB1cnJyOjptYXBfZGYoc3VtbWFyaXplX3Jlc3VsdHMsIAogICAgICAgICAgICAgICAgIGxldmVsPSdmYW1pbHknLAogICAgICAgICAgICAgICAgLmlkPSdLaW5nZG9tJwogICAgICAgICAgICAgICAgKSAlPiUKICBtdXRhdGUoS2luZ2RvbSA9IGZhY3RvcihLaW5nZG9tLGxldmVscz1jKCdNZXRhem9hJywnVmlyaWRpcGxhbnRhZScsJ0Z1bmdpJyksb3JkZXJlZCA9IFQpLAogICAgICAgICBmYW1pbHlfaW5fdHJhaW5pbmcgPSBGKSkgJT4lCiAgbXV0YXRlKGZhbWlseV9pbl90cmFpbmluZyA9IGMoJ0ZhbWlseVxubm90IGluIHRyYWluaW5nIHNldCcsICdGYW1pbHlcbmluIHRyYWluaW5nIHNldCcpW2ZhbWlseV9pbl90cmFpbmluZysxXSkKc3VtbWFyeV9TUkFfYnlfa2luZ2RvbSAKCgpwX1NSQV9mYW1pbGllcyA9IHBsb3RfYXJlYShzdW1tYXJ5X1NSQV9ieV9raW5nZG9tICwKICAgICAgICAgIHJlbGF0aXZlPUZBTFNFLAogICAgICAgICAgeGxpbV9hbGwgPSBGQUxTRSwKICAgICAgICAgIHRpdGxlPSdFdWthcnlvdGUgZmFtaWxpZXMnKSArIAogIGZhY2V0X2dyaWQoZmFtaWx5X2luX3RyYWluaW5nfktpbmdkb20pICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDUwMCwxMDAwMCkqMTAwMCxleHBhbmQgPSBGQUxTRSkgKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpCgpwcmludChwX1NSQV9mYW1pbGllcykKCgpzYWZlX2dnc2F2ZSAoJ2ltYWdlc19tYW51c2NyaXB0L2ZpZzNfU1JBX2FjY3VyYWN5LnBkZicsIHdpZHRoPTUsaGVpZ2h0ID0gNCkKc2FmZV9nZ3NhdmUgKCdpbWFnZXNfbWFudXNjcmlwdC9maWczX1NSQV9hY2N1cmFjeS5wbmcnLCB3aWR0aD01LGhlaWdodCA9IDQsZHBpID0gMTIwMCkKYGBgCiMjIFZhcktvZGVzCldlIHJlcGVhdGVkIHRoaXMgYW5hbHlzaXMgd2l0aCB2YXJLb2Rlcy4gTGV0J3MgY29tcGFyZSBub3cuCmBgYHtyfQpwbGFuKHNlcXVlbnRpYWwpCgp2YXJLb2Rlcl9TUkFfcmVzdWx0c192ayA9IHJlYWRfY3N2KCdhbGxfU1JBX2V1a2FyeW90ZV9mYW1pbGllcy92YXJrb2Rlcl9xdWVyeV9yZXN1bHRzL3ByZWRpY3Rpb25zLmNzdicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbF90eXBlcyA9IGxpc3QocXVlcnlfYmFzZXBhaXJzID0gImMiKSkgJT4lCiAgc2VsZWN0KC0xKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChxdWVyeV9iYXNlcGFpcnMsJ14wKlsxMjVdMCtLJCcpKSAlPiUKICBtdXRhdGUocXVlcnlfYnAgPSBhcy5udW1lcmljKHN0cl9yZW1vdmUocXVlcnlfYmFzZXBhaXJzLCdLJykpKjEwMDApCgp2YXJLb2Rlcl9TUkFfcmVzdWx0c192ayA9IHZhcktvZGVyX1NSQV9yZXN1bHRzX3ZrICU+JQogIG11dGF0ZShxdWVyeV9sYWJlbHMgPSBzdHJfcmVtb3ZlKGFjdHVhbF9sYWJlbHMsIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpICU+JSB1bmxpc3QsCiAgICAgICAgIHByZWRpY3RlZF9saXN0ID0gc3RyX3NwbGl0KHByZWRpY3RlZF9sYWJlbHMsJzsnKQogICkgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZShmYW1pbHlfY29ycmVjdCA9IHF1ZXJ5X2xhYmVscyAlaW4lIHByZWRpY3RlZF9saXN0LAogICAgICAgICBmYW1pbHlfaW5jb3JyZWN0ID0gaWZlbHNlKGlzLm5hKHByZWRpY3RlZF9sYWJlbHMpLEZBTFNFLGFueSghKHByZWRpY3RlZF9saXN0ICVpbiUgcXVlcnlfbGFiZWxzKSkpLAogICAgICAgICBmYW1pbHlfaW5fdHJhaW5pbmcgPSBUUlVFKSAlPiUKICBzZWxlY3QobWF0Y2hlcygiXlteMC05XSIpKSAKICAKCnZhcktvZGVyX1NSQV9yZXN1bHRzX25vdGluY2x1ZGVkX3ZrID0gcmVhZF9jc3YoJ2FsbF9TUkFfZXVrYXJ5b3RlX2ZhbWlsaWVzL3ZhcmtvZGVyX3F1ZXJ5X25vdGluY2x1ZGVkX3Jlc3VsdHMvcHJlZGljdGlvbnMuY3N2JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sX3R5cGVzID0gbGlzdChxdWVyeV9iYXNlcGFpcnMgPSAiYyIpKSAlPiUKICBzZWxlY3QoLTEpICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KHF1ZXJ5X2Jhc2VwYWlycywnXjAqWzEyNV0wK0skJykpICU+JQogIG11dGF0ZShxdWVyeV9icCA9IGFzLm51bWVyaWMoc3RyX3JlbW92ZShxdWVyeV9iYXNlcGFpcnMsJ0snKSkqMTAwMCkKClNSQV90YXhsYWJlbHNfbm90aW5jbHVkZWRfdmsgPSBzdHJfcmVtb3ZlKHZhcktvZGVyX1NSQV9yZXN1bHRzX3ZrJGFjdHVhbF9sYWJlbHMsIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIAogIHN0cl9zcGxpdCgnOycpICU+JSAKICB1bmxpc3QgJT4lIAogIHVuaXF1ZSAKCnZhcktvZGVyX1NSQV9yZXN1bHRzX25vdGluY2x1ZGVkX3ZrID0gdmFyS29kZXJfU1JBX3Jlc3VsdHNfbm90aW5jbHVkZWRfdmsgJT4lCiAgbXV0YXRlKHF1ZXJ5X2xhYmVscyA9IHN0cl9yZW1vdmUoYWN0dWFsX2xhYmVscywiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JykgJT4lIHVubGlzdCwKICAgICAgICAgcHJlZGljdGVkX2xpc3QgPSBzdHJfc3BsaXQocHJlZGljdGVkX2xhYmVscywnOycpCiAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKGZhbWlseV9jb3JyZWN0ID0gcXVlcnlfbGFiZWxzICVpbiUgcHJlZGljdGVkX2xpc3QsCiAgICAgICAgIGZhbWlseV9pbmNvcnJlY3QgPSBpZmVsc2UoaXMubmEocHJlZGljdGVkX2xhYmVscyksRkFMU0UsYW55KCEocHJlZGljdGVkX2xpc3QgJWluJSBxdWVyeV9sYWJlbHMpKSksCiAgICAgICAgIGZhbWlseV9pbl90cmFpbmluZyA9IEZBTFNFKSAlPiUKICBzZWxlY3QobWF0Y2hlcygiXlteMC05XSIpKQoKIyBTdW1tYXJ5IHN0YXRpc3RpY3MKU1JBX3N1bW1hcnlfZmFtaWx5X3ZrID0gYmluZF9yb3dzKAogIHN1bW1hcml6ZV9yZXN1bHRzKHZhcktvZGVyX1NSQV9yZXN1bHRzX3ZrLCdmYW1pbHknKSAlPiUgbXV0YXRlKGZhbWlseV9pbl90cmFpbmluZyA9IFRSVUUpLAogIHN1bW1hcml6ZV9yZXN1bHRzKHZhcktvZGVyX1NSQV9yZXN1bHRzX25vdGluY2x1ZGVkX3ZrLCdmYW1pbHknKSAlPiUgbXV0YXRlKGZhbWlseV9pbl90cmFpbmluZyA9IEZBTFNFKQopCgpOX3NhbXBfdmsgPSBTUkFfc3VtbWFyeV9mYW1pbHlfdmsgJT4lCiAgZ3JvdXBfYnkocXVlcnlfYnAsIGZhbWlseV9pbl90cmFpbmluZykgJT4lCiAgc3VtbWFyaXNlKE4gPSBzdW0oTikpCgojIEtpbmdkb20gc3VtbWFyeQpzdW1tYXJ5X1NSQV9ieV9raW5nZG9tX3ZrID0gcmVhZF9jc3YoJ2FsbF9TUkFfZXVrYXJ5b3RlX2ZhbWlsaWVzL3J1bnNfdG9fZG93bmxvYWRfZGF0YS5jc3YnKSAlPiUKICBzZWxlY3Qoc2FtcGxlX2lkID0gUnVuLCBLaW5nZG9tKSAlPiUKICByaWdodF9qb2luKHZhcktvZGVyX1NSQV9yZXN1bHRzX3ZrKSAlPiUKICBzcGxpdCguJEtpbmdkb20pICU+JQogIHB1cnJyOjptYXBfZGYoc3VtbWFyaXplX3Jlc3VsdHMsIAogICAgICAgICAgICAgICAgbGV2ZWw9J2ZhbWlseScsCiAgICAgICAgICAgICAgICAuaWQ9J0tpbmdkb20nKSAlPiUKICBtdXRhdGUoS2luZ2RvbSA9IGZhY3RvcihLaW5nZG9tLGxldmVscz1jKCdNZXRhem9hJywnVmlyaWRpcGxhbnRhZScsJ0Z1bmdpJyksb3JkZXJlZCA9IFRSVUUpLAogICAgICAgICBmYW1pbHlfaW5fdHJhaW5pbmcgPSBUUlVFKSAlPiUKICBiaW5kX3Jvd3MoCiAgICByZWFkX2NzdignYWxsX1NSQV9ldWthcnlvdGVfZmFtaWxpZXMvcnVuc19ub3RpbmNsdWRlZF90b19kb3dubG9hZF9kYXRhLmNzdicpICU+JQogICAgICBzZWxlY3Qoc2FtcGxlX2lkID0gUnVuLCBLaW5nZG9tKSAlPiUKICAgICAgcmlnaHRfam9pbih2YXJLb2Rlcl9TUkFfcmVzdWx0c19ub3RpbmNsdWRlZF92aykgJT4lCiAgICAgIHNwbGl0KC4kS2luZ2RvbSkgJT4lCiAgICAgIHB1cnJyOjptYXBfZGYoc3VtbWFyaXplX3Jlc3VsdHMsIAogICAgICAgICAgICAgICAgICAgIGxldmVsPSdmYW1pbHknLAogICAgICAgICAgICAgICAgICAgIC5pZD0nS2luZ2RvbScpICU+JQogICAgICBtdXRhdGUoS2luZ2RvbSA9IGZhY3RvcihLaW5nZG9tLGxldmVscz1jKCdNZXRhem9hJywnVmlyaWRpcGxhbnRhZScsJ0Z1bmdpJyksb3JkZXJlZCA9IFRSVUUpLAogICAgICAgICAgICAgZmFtaWx5X2luX3RyYWluaW5nID0gRkFMU0UpCiAgKSAlPiUKICBtdXRhdGUoZmFtaWx5X2luX3RyYWluaW5nID0gYygnRmFtaWx5XG5ub3QgaW4gdHJhaW5pbmcgc2V0JywgJ0ZhbWlseVxuaW4gdHJhaW5pbmcgc2V0JylbZmFtaWx5X2luX3RyYWluaW5nKzFdKQoKIyBGaW5hbCBwbG90CnBfU1JBX2ZhbWlsaWVzX3ZrID0gcGxvdF9hcmVhKHN1bW1hcnlfU1JBX2J5X2tpbmdkb21fdmssCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVsYXRpdmU9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgeGxpbV9hbGwgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZT0nRXVrYXJ5b3RlIGZhbWlsaWVzJykgKyAKICBmYWNldF9ncmlkKGZhbWlseV9pbl90cmFpbmluZ35LaW5nZG9tKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW09Yyg1MDAsMTAwMDApKjEwMDAsZXhwYW5kID0gRkFMU0UpICsKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApKQoKcF9TUkFfZmFtaWxpZXNfdmsKCmBgYAoKVGhlIGdyYXBoIGxvb2tzIGFsbW9zdCBpZGVudGljYWwsIGxldCdzIG1ha2UgYSB0YWJsZSBjb21wYXJpbmcgc2lkZS1ieS1zaWRlLiBJdCBzZWVtcyByZkNHUnMgYXJlIHNsaWdodGx5IGJldHRlci4KCmBgYHtyfQpmdWxsX2pvaW4oCnNlbGVjdChzdW1tYXJ5X1NSQV9ieV9raW5nZG9tX3ZrLEtpbmdkb20scXVlcnlfYnAscmVzdWx0LHAsZmFtaWx5X2luX3RyYWluaW5nKSwKc2VsZWN0KHN1bW1hcnlfU1JBX2J5X2tpbmdkb20sLEtpbmdkb20scXVlcnlfYnAscmVzdWx0LHAsZmFtaWx5X2luX3RyYWluaW5nKSwKYnk9am9pbl9ieShLaW5nZG9tLHF1ZXJ5X2JwLHJlc3VsdCxmYW1pbHlfaW5fdHJhaW5pbmcpLApzdWZmaXggPSBjKCcudmFyS29kZScsJy5yZkNHUicpCikgJT4lCiAgbXV0YXRlKGRpZmZlcmVuY2UgPSBwLnZhcktvZGUtYHAucmZDR1JgKSAlPiUKICBhcnJhbmdlKGZhbWlseV9pbl90cmFpbmluZyxyZXN1bHQscXVlcnlfYnAsS2luZ2RvbSkKYGBgCgoKCiMgT3RoZXIgc3BlY2llcy1sZXZlbCBkYXRhc2V0cwoKIyMgdmFyS29kZXMKCk5vdyB3ZSB3aWxsIG1ha2UgYSBzbWFsbCBmaWd1cmUgdG8gaW5jbHVkZSB0aGUgYWRkaXRpb25hbCBkYXRhc2V0cyBpbiB3aGljaCB3ZSBhcHBsaWVkIHZhcktvZGluZy4KCkluIHRoZXNlIGNhc2VzLCB3ZSBjaG9zZSBhIHRlc3Qgc2V0IHRoYXQgaW5jbHVkZWQgYm90aCB0YXhhIGluIHRoZSB0cmFpbmluZyBzZXQgYW5kIHRheGEgbm90IGluIHRoZSB0cmFpbmluZyBzZXQsIHNvIHdlIHdpbGwgZ3JhcGggYm90aCBzZXBhcmF0ZWx5LiBUaGlzIGlzIGRlbm90ZWQgYnkgYSBjb2x1bW4gbmFtZWQgYGluX3RyYWluaW5nX21vZGVsYC4gTGV0J3Mgc3RhcnQgYnkgcmVhZGluZyByZXN1bHRzLgoKTGV0J3MgZGVmaW5lIGEgZnVuY3Rpb24gdG8gcmVhZCBhbmQgcHJvY2VzcyBwcmVkaWN0aW9uczoKCmBgYHtyfQpyZWFkX2FuZF9wcm9jZXNzX290aGVycyA9IGZ1bmN0aW9uKGluZmlsZSl7CiAgCnZhcmtvZGVyX3Jlc3VsdHMgPSByZWFkX2NzdihpbmZpbGUpICU+JSAKICBtdXRhdGUoc2FtcGxlX2lkID0gYXMuY2hhcmFjdGVyKHNhbXBsZV9pZCkpICU+JQogIHNlbGVjdCgtMSkgJT4lCiAgcmVuYW1lKHF1ZXJ5X2JwID0gcXVlcnlfYmFzZXBhaXJzKSAKCgphbGxfdGF4bGFiZWxzID0gc3RyX3JlbW92ZSh2YXJrb2Rlcl9yZXN1bHRzJGFjdHVhbF9sYWJlbHMsIjsqbG93X3F1YWxpdHk6VHJ1ZTsqIikgJT4lIHN0cl9zcGxpdCgnOycpICU+JSB1bmxpc3QgJT4lIHVuaXF1ZQoKdmFya29kZXJfcmVzdWx0cyA9IHZhcmtvZGVyX3Jlc3VsdHMgJT4lCiAgbXV0YXRlKHF1ZXJ5X2xhYmVscyA9IHN0cl9yZW1vdmUoYWN0dWFsX2xhYmVscywiOypsb3dfcXVhbGl0eTpUcnVlOyoiKSAlPiUgc3RyX3NwbGl0KCc7JyksCiAgICAgICAgIHByZWRpY3RlZF9saXN0ID0gc3RyX3NwbGl0KHByZWRpY3RlZF9sYWJlbHMsJzsnKQogICAgICAgICApICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUodGF4b25fY29ycmVjdCA9IGFueShxdWVyeV9sYWJlbHMgJWluJSBwcmVkaWN0ZWRfbGlzdCksCiAgICAgICAgIHRheG9uX2luY29ycmVjdCA9IGFueSghKHByZWRpY3RlZF9saXN0WyFpcy5uYShwcmVkaWN0ZWRfbGlzdCldICVpbiUgcXVlcnlfbGFiZWxzKSkKICAgICAgICAgKQoKcmV0dXJuKHZhcmtvZGVyX3Jlc3VsdHMpCn0KYGBgCgpOb3cgbGV0J3MgYXBwbHkgdGhpcyBmdW5jdGlvbiB0byBhbGwgZmlsZXMuCmBgYHtyfQpwcmVkaWN0aW9uX2ZpbGVzID0gbGlzdC5maWxlcygnb3RoZXJfZGF0YXNldHMvdmFyS29kZS8nLHBhdHRlcm4gPSAncHJlZGljdGlvbnMuK2NzdicsZnVsbC5uYW1lcyA9IFQscmVjdXJzaXZlID0gVCkKbmFtZXMocHJlZGljdGlvbl9maWxlcykgPSBiYXNlbmFtZShwcmVkaWN0aW9uX2ZpbGVzKSAlPiUgc3RyX2V4dHJhY3QoIi4qKD89X3ByZWRpY3Rpb25zXFwuY3N2KSIpCgpvdGhlcl9yZXN1bHRzID0gcHVycnI6Om1hcF9kZnIocHJlZGljdGlvbl9maWxlcywgcmVhZF9hbmRfcHJvY2Vzc19vdGhlcnMsIC5pZD0nZGF0YXNldCcpCm90aGVyX3Jlc3VsdHMKYGBgCgpMZXQncyBub3cgc3VtbWFyaXplIGJ5IGRhdGFzZXQgYW5kIHNlcGFyYXRlbHkgZm9yIHRheGEgaW5jbHVkZWQgYW5kIGV4Y2x1ZGVkIGZyb20gdGhlIHRyYWluaW5nIHNldC4KCmBgYHtyfQpzdW1tYXJ5X290aGVycyA9IG90aGVyX3Jlc3VsdHMgJT4lCiAgc3BsaXQoaW50ZXJhY3Rpb24ob3RoZXJfcmVzdWx0cyRkYXRhc2V0LCBvdGhlcl9yZXN1bHRzJGluX3RyYWluaW5nX21vZGVsKSkgJT4lCiAgcHVycnI6Om1hcF9kZnIoc3VtbWFyaXplX3Jlc3VsdHMsIGxldmVsID0gJ3RheG9uJywgLmlkID0gJ2NvbWInKSAlPiUKICBzZXBhcmF0ZShjb21iLCBpbnRvID0gYygiZGF0YXNldCIsICJ0YXhvbl9pbl90cmFpbmluZ19yYXciKSwgc2VwID0gIlxcLiIpICU+JQogIG11dGF0ZSh0YXhvbl9pbl90cmFpbmluZyA9IHRheG9uX2luX3RyYWluaW5nX3JhdyA9PSAneWVzJykgJT4lCiAgc2VsZWN0KC10YXhvbl9pbl90cmFpbmluZ19yYXcpICU+JQogIG11dGF0ZSh0YXhvbl9pbl90cmFpbmluZyA9IGMoJ1RheG9uIG5vdCBpbiB0cmFpbmluZyBzZXQnLCAnVGF4b24gaW4gdHJhaW5pbmcgc2V0JylbdGF4b25faW5fdHJhaW5pbmcrMV0sCiAgICAgICAgIGRhdGFzZXQgPSBzdHJfcmVwbGFjZShkYXRhc2V0LCAiXiguKSIsIH50b3VwcGVyKC54KSkpICU+JQogIG11dGF0ZShyZXN1bHQgPSBmYWN0b3IocmVzdWx0LAogICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoImNvcnJlY3QiLCAiYW1iaWd1b3VzIiwgImluY29uY2x1c2l2ZSIsICJpbmNvcnJlY3QiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQ9VCkpCgpzdW1tYXJ5X290aGVycwoKYGBgCk5vdyBsZXQncyBwbG90CmBgYHtyfQpwX290aGVycyA9IGdncGxvdChzdW1tYXJ5X290aGVycyAsIGFlcyh4ID0gZGF0YXNldCwgeSA9IE4sIGZpbGwgPSByZXN1bHQpKSArCiAgZ2VvbV9jb2woKSsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHNldE5hbWVzKFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg0LCAiQWNjZW50IiksIGMoImNvcnJlY3QiLCAiYW1iaWd1b3VzIiwgImluY29uY2x1c2l2ZSIsICJpbmNvcnJlY3QiKSkpICsKICAgIHNjYWxlX2FscGhhX21hbnVhbCh2YWx1ZXM9YygwLjUsMSkpICsKICAgIGdndGl0bGUoJ090aGVyIGRhdGFzZXRzJykgKwogICAgeWxhYignTnVtYmVyIG9mIHNhbXBsZXMnKSArCiAgICB4bGFiKCdUYXhvbicpICsKICAgIHRoZW1lX2ZldygpICsKICAgICAgc2NhbGVfeV9jb250aW51b3VzKG1pbm9yX2JyZWFrcyA9IHdhaXZlcigpKSArCiAgICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSwKICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC41KSksCiAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBncmF5KDAuNiksbGluZXR5cGUgPSAyKSwKICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChmYWNlPSdpdGFsaWMnKSwKICAgICAgICAgICAgcGFuZWwub250b3AgPSBUUlVFKSArCiAgICBjb29yZF9jYXJ0ZXNpYW4oZXhwYW5kPUZBTFNFKSArCiAgICBmYWNldF9ncmlkKHRheG9uX2luX3RyYWluaW5nfi4pICsKICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDMwLGhqdXN0ID0gMSkpCiAgCnBfb3RoZXJzCgpgYGAKCiMjIENHUgpMZXQncyByZXBlYXQgZXZlcnl0aGluZyBmb3IgQ0dSIHJlcHJlc2VudGF0aW9uIG5vdwpgYGB7cn0KIyBSZWFkIHByZWRpY3Rpb24gZmlsZXMKcHJlZGljdGlvbl9maWxlc19DR1IgPSBsaXN0LmZpbGVzKCdvdGhlcl9kYXRhc2V0cy9jZ3IvJyxwYXR0ZXJuID0gJ3ByZWRpY3Rpb25zLitjc3YnLGZ1bGwubmFtZXMgPSBUUlVFLHJlY3Vyc2l2ZSA9IFQpCm5hbWVzKHByZWRpY3Rpb25fZmlsZXNfQ0dSKSA9IGJhc2VuYW1lKHByZWRpY3Rpb25fZmlsZXNfQ0dSKSAlPiUgc3RyX2V4dHJhY3QoIi4qKD89X3ByZWRpY3Rpb25zXFwuY3N2KSIpCm90aGVyX3Jlc3VsdHNfQ0dSID0gcHVycnI6Om1hcF9kZnIocHJlZGljdGlvbl9maWxlc19DR1IsIHJlYWRfYW5kX3Byb2Nlc3Nfb3RoZXJzLCAuaWQ9J2RhdGFzZXQnKQoKIyBDcmVhdGUgc3VtbWFyeQpzdW1tYXJ5X290aGVyc19DR1IgPSBvdGhlcl9yZXN1bHRzX0NHUiAlPiUKICBzcGxpdChpbnRlcmFjdGlvbihvdGhlcl9yZXN1bHRzX0NHUiRkYXRhc2V0LCBvdGhlcl9yZXN1bHRzX0NHUiRpbl90cmFpbmluZ19tb2RlbCkpICU+JQogIHB1cnJyOjptYXBfZGZyKHN1bW1hcml6ZV9yZXN1bHRzLCBsZXZlbCA9ICd0YXhvbicsIC5pZCA9ICdjb21iJykgJT4lCiAgc2VwYXJhdGUoY29tYiwgaW50byA9IGMoImRhdGFzZXQiLCAidGF4b25faW5fdHJhaW5pbmdfcmF3IiksIHNlcCA9ICJcXC4iKSAlPiUKICBtdXRhdGUodGF4b25faW5fdHJhaW5pbmcgPSB0YXhvbl9pbl90cmFpbmluZ19yYXcgPT0gJ3llcycpICU+JQogIHNlbGVjdCgtdGF4b25faW5fdHJhaW5pbmdfcmF3KSAlPiUKICBtdXRhdGUodGF4b25faW5fdHJhaW5pbmcgPSBjKCdUYXhvbiBub3QgaW4gdHJhaW5pbmcgc2V0JywgJ1RheG9uIGluIHRyYWluaW5nIHNldCcpW3RheG9uX2luX3RyYWluaW5nKzFdLAogICAgICAgICBkYXRhc2V0ID0gc3RyX3JlcGxhY2UoZGF0YXNldCwgIl4oLikiLCB+dG91cHBlcigueCkpKSAlPiUKICBtdXRhdGUocmVzdWx0ID0gZmFjdG9yKHJlc3VsdCwKICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoImNvcnJlY3QiLCAiYW1iaWd1b3VzIiwgImluY29uY2x1c2l2ZSIsICJpbmNvcnJlY3QiKSwKICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZD1UUlVFKSkKCiMgQ3JlYXRlIHBsb3QKcF9vdGhlcnNfQ0dSID0gZ2dwbG90KHN1bW1hcnlfb3RoZXJzX0NHUiwgYWVzKHggPSBkYXRhc2V0LCB5ID0gTiwgZmlsbCA9IHJlc3VsdCkpICsKICBnZW9tX2NvbCgpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBzZXROYW1lcyhSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNCwgIkFjY2VudCIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiY29ycmVjdCIsICJhbWJpZ3VvdXMiLCAiaW5jb25jbHVzaXZlIiwgImluY29ycmVjdCIpKSkgKwogIHNjYWxlX2FscGhhX21hbnVhbCh2YWx1ZXM9YygwLjUsMSkpICsKICBnZ3RpdGxlKCdPdGhlciBkYXRhc2V0cycpICsKICB5bGFiKCdOdW1iZXIgb2Ygc2FtcGxlcycpICsKICB4bGFiKCdUYXhvbicpICsKICB0aGVtZV9mZXcoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG1pbm9yX2JyZWFrcyA9IHdhaXZlcigpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBncmF5KDAuNSkpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBncmF5KDAuNiksbGluZXR5cGUgPSAyKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZT0naXRhbGljJyksCiAgICAgICAgcGFuZWwub250b3AgPSBUUlVFKSArCiAgY29vcmRfY2FydGVzaWFuKGV4cGFuZD1GQUxTRSkgKwogIGZhY2V0X2dyaWQodGF4b25faW5fdHJhaW5pbmd+LikgKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAzMCxoanVzdCA9IDEpKQoKcF9vdGhlcnNfQ0dSCgpzYWZlX2dnc2F2ZSAoJ2ltYWdlc19tYW51c2NyaXB0L2ZpZzNfb3RoZXJzX2FjY3VyYWN5LnBkZicsIHdpZHRoPTMuNSxoZWlnaHQgPSAzKQpzYWZlX2dnc2F2ZSAoJ2ltYWdlc19tYW51c2NyaXB0L2ZpZzNfb3RoZXJzX2FjY3VyYWN5LnBuZycsIHdpZHRoPTMuNSxoZWlnaHQgPSAzLGRwaSA9IDEyMDApCmBgYAoKIyBTUkE6IGFsbCB0YXhhCgojIyB1bnBhY2tfbGFiZWxzIGZ1bmN0aW9uClRoaXMgZnVuY3Rpb24gdGFrZXMgYSBzdHJpbmcgb2Ygc2VtaWNvbG9uLXNlcGFyYXRlZCBrZXktdmFsdWUgcGFpcnMgYW5kIGNvbnZlcnRzIGl0IGludG8gYSBzdHJ1Y3R1cmVkIGRhdGFmcmFtZS4gSXQgc3BsaXRzIHRoZSBpbnB1dCBzdHJpbmcgb24gc2VtaWNvbG9ucywgdGhlbiBmdXJ0aGVyIHNwbGl0cyBlYWNoIHBhaXIgb24gY29sb25zIHRvIHNlcGFyYXRlIGtleXMgYW5kIHZhbHVlcy4gVGhlIHJlc3VsdCBpcyBhIHR3by1jb2x1bW4gZGF0YWZyYW1lIHdpdGggJ2tleScgYW5kICd2YWx1ZScgY29sdW1ucywgcHJvdmlkaW5nIGFuIG9yZ2FuaXplZCByZXByZXNlbnRhdGlvbiBvZiB0aGUgbGFiZWwgZGF0YS4KYGBge3J9CnVucGFja19sYWJlbHMgPSBmdW5jdGlvbih4KXsKICAjIFNwbGl0IHRoZSBpbnB1dCBzdHJpbmcgYnkgJzsnCiAga3ZzID0gc3Ryc3BsaXQoeCwgJzsnKVtbMV1dCiAgCiAgIyBTcGxpdCBlYWNoIGtleS12YWx1ZSBwYWlyIGJ5ICc6JwogIGt2c19zcGxpdCA9IHN0cnNwbGl0KGt2cywgJzonKQogIAogICMgQ3JlYXRlIGEgZGF0YWZyYW1lIHdpdGggY29sdW1ucyAia2V5IiBhbmQgInZhbHVlIgogIGRmID0gZGF0YS5mcmFtZSgKICAgIGtleSA9IHNhcHBseShrdnNfc3BsaXQsIGBbYCwgMSksCiAgICB2YWx1ZSA9IHNhcHBseShrdnNfc3BsaXQsIGBbYCwgMiksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCiAgCiAgcmV0dXJuKGRmKQp9CmBgYAoKIyMgc3VtbWFyaXplX2NvbXBhcmlzb24gZnVuY3Rpb24KVGhpcyBmdW5jdGlvbiBjb21wYXJlcyB0d28gc2V0cyBvZiBsYWJlbHMgKGFjdHVhbCBhbmQgcHJlZGljdGVkKSBhbmQgZ2VuZXJhdGVzIGEgc3VtbWFyeSBvZiB0aGVpciBhZ3JlZW1lbnQuIEl0IHByb2Nlc3NlcyBhbGwgdW5pcXVlIGtleXMgZm91bmQgaW4gYm90aCBzZXRzLCBleGNsdWRpbmcgY2VydGFpbiB0YXhvbm9teS1yZWxhdGVkIGtleXMsIGFuZCBpbmNsdWRlcyBhIHNwZWNpYWwgJ1RheG9ub215X2FsbCcga2V5LiBGb3IgZWFjaCBrZXksIGl0IGNhbGN1bGF0ZXMgdGhlIG51bWJlciBvZiBhY3R1YWwgYW5kIHByZWRpY3RlZCB2YWx1ZXMsIHRydWUgcG9zaXRpdmVzIChUUCksIGZhbHNlIG5lZ2F0aXZlcyAoRk4pLCBhbmQgZmFsc2UgcG9zaXRpdmVzIChGUCkuIFRoZSBvdXRwdXQgaXMgYSBkYXRhZnJhbWUgc3VtbWFyaXppbmcgdGhlc2UgbWV0cmljcyBmb3IgZWFjaCBrZXksIGVuYWJsaW5nIGRldGFpbGVkIGFuYWx5c2lzIG9mIHByZWRpY3Rpb24gYWNjdXJhY3kgYWNyb3NzIGRpZmZlcmVudCBsYWJlbCB0eXBlcy4KYGBge3J9CnN1bW1hcml6ZV9jb21wYXJpc29uIDwtIGZ1bmN0aW9uKGFjdHVhbCwgcHJlZGljdGVkKSB7CiAgCiAgIyBFbnN1cmUgdGhlIGtleSBjb2x1bW5zIGFyZSBvZiB0eXBlIGNoYXJhY3RlciB0byBhdm9pZCBmYWN0b3IgbGV2ZWwgaXNzdWVzCiAgYWN0dWFsJGtleSA8LSBhcy5jaGFyYWN0ZXIoYWN0dWFsJGtleSkKICBwcmVkaWN0ZWQka2V5IDwtIGFzLmNoYXJhY3RlcihwcmVkaWN0ZWQka2V5KQogIAogICMgR2V0IHRoZSB1bmlxdWUga2V5cyBmcm9tIGJvdGggYWN0dWFsIGFuZCBwcmVkaWN0ZWQsIGV4Y2x1ZGluZyBzcGVjaWZpYyBrZXlzCiAgYWxsX2tleXMgPC0gdW5pcXVlKGMoYWN0dWFsJGtleSwgcHJlZGljdGVkJGtleSkpCiAgZmlsdGVyZWRfa2V5cyA8LSBzZXRkaWZmKGFsbF9rZXlzLCBjKCJUYXhvbm9teV9ub19yYW5rIiwgIlRheG9ub215X2NsYWRlIikpCiAgCiAgIyBBZGQgIlRheG9ub215X2FsbCIga2V5IHRvIHRoZSBsaXN0IG9mIGtleXMKICBpZiAoISJUYXhvbm9teV9hbGwiICVpbiUgZmlsdGVyZWRfa2V5cykgewogICAgZmlsdGVyZWRfa2V5cyA8LSBjKGZpbHRlcmVkX2tleXMsICJUYXhvbm9teV9hbGwiKQogIH0KICAKICAjIEluaXRpYWxpemUgYSByZXN1bHQgZGF0YWZyYW1lCiAgcmVzdWx0IDwtIGRhdGEuZnJhbWUoCiAgICBrZXkgPSBmaWx0ZXJlZF9rZXlzLAogICAgTl9hY3R1YWwgPSBpbnRlZ2VyKGxlbmd0aChmaWx0ZXJlZF9rZXlzKSksCiAgICBOX3ByZWRpY3RlZCA9IGludGVnZXIobGVuZ3RoKGZpbHRlcmVkX2tleXMpKSwKICAgIFRQID0gaW50ZWdlcihsZW5ndGgoZmlsdGVyZWRfa2V5cykpLAogICAgRk4gPSBpbnRlZ2VyKGxlbmd0aChmaWx0ZXJlZF9rZXlzKSksCiAgICBGUCA9IGludGVnZXIobGVuZ3RoKGZpbHRlcmVkX2tleXMpKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKICAKICBmb3IgKGtleSBpbiBmaWx0ZXJlZF9rZXlzKSB7CiAgICBpZiAoa2V5ID09ICJUYXhvbm9teV9hbGwiKSB7CiAgICAgIGFjdHVhbF92YWx1ZXMgPC0gYWN0dWFsJHZhbHVlW2dyZXBsKCJeVGF4b25vbXlfIiwgYWN0dWFsJGtleSldCiAgICAgIHByZWRpY3RlZF92YWx1ZXMgPC0gcHJlZGljdGVkJHZhbHVlW2dyZXBsKCJeVGF4b25vbXlfIiwgcHJlZGljdGVkJGtleSldCiAgICB9IGVsc2UgewogICAgICBhY3R1YWxfdmFsdWVzIDwtIGFjdHVhbCR2YWx1ZVthY3R1YWwka2V5ID09IGtleV0KICAgICAgcHJlZGljdGVkX3ZhbHVlcyA8LSBwcmVkaWN0ZWQkdmFsdWVbcHJlZGljdGVkJGtleSA9PSBrZXldCiAgICB9CiAgICAKICAgIE5fYWN0dWFsIDwtIGxlbmd0aChhY3R1YWxfdmFsdWVzKQogICAgTl9wcmVkaWN0ZWQgPC0gbGVuZ3RoKHByZWRpY3RlZF92YWx1ZXMpCiAgICBUUCA8LSBzdW0oYWN0dWFsX3ZhbHVlcyAlaW4lIHByZWRpY3RlZF92YWx1ZXMpCiAgICBGTiA8LSBzdW0oIWFjdHVhbF92YWx1ZXMgJWluJSBwcmVkaWN0ZWRfdmFsdWVzKQogICAgRlAgPC0gc3VtKCFwcmVkaWN0ZWRfdmFsdWVzICVpbiUgYWN0dWFsX3ZhbHVlcykKICAgIAogICAgcmVzdWx0W3Jlc3VsdCRrZXkgPT0ga2V5LCBdIDwtIGxpc3Qoa2V5LCBOX2FjdHVhbCwgTl9wcmVkaWN0ZWQsIFRQLCBGTiwgRlApCiAgfQogIAogIHJldHVybihyZXN1bHQpCn0KYGBgCgojIyBwcm9jZXNzX2RhdGFmcmFtZV9wYXJhbGxlbCBmdW5jdGlvbgpUaGlzIGZ1bmN0aW9uIHBlcmZvcm1zIHBhcmFsbGVsIHByb2Nlc3Npbmcgb24gYSBkYXRhZnJhbWUgdG8gZXh0cmFjdCBhbmQgc3RydWN0dXJlIGxhYmVsIGluZm9ybWF0aW9uLiBJdCB1c2VzIG11bHRpcGxlIGNvcmVzIHRvIHNwZWVkIHVwIHByb2Nlc3Npbmcgb2YgbGFyZ2UgZGF0YXNldHMuIEZvciBlYWNoIHJvdywgaXQgdW5wYWNrcyB0aGUgYWN0dWFsIGFuZCBwcmVkaWN0ZWQgbGFiZWxzLCBleHRyYWN0cyBsaWJyYXJ5IHN0cmF0ZWd5IGFuZCBwbGF0Zm9ybSBpbmZvcm1hdGlvbiwgYW5kIHN0cnVjdHVyZXMgdGhpcyBkYXRhIGludG8gYSBuZXcgZGF0YWZyYW1lLiBUaGUgcmVzdWx0IGlzIGEgcHJvY2Vzc2VkIGRhdGFmcmFtZSB3aXRoIHVucGFja2VkIGxhYmVsIGluZm9ybWF0aW9uIGFuZCBhZGRpdGlvbmFsIG1ldGFkYXRhLCBvcHRpbWl6ZWQgZm9yIGZ1cnRoZXIgYW5hbHlzaXMuCgpgYGB7cn0KcHJvY2Vzc19kYXRhZnJhbWVfcGFyYWxsZWwgPC0gZnVuY3Rpb24oZGYsIG51bV9jb3JlcyA9IDE2KSB7CiAgIyBTZXQgdXAgcGFyYWxsZWwgcHJvY2Vzc2luZwogIHBsYW4obXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gbnVtX2NvcmVzKQogIAogIHByb2Nlc3NlZF9kZiA8LSBkZiAlPiUKICAgIHNwbGl0KDE6bnJvdyguKSkgJT4lICAjIFNwbGl0IHRoZSBkYXRhZnJhbWUgaW50byBhIGxpc3Qgb2Ygcm93cwogICAgZnV0dXJlX21hcF9kZnIofiB7CiAgICAgIGFjdHVhbCA9IHVucGFja19sYWJlbHMoLngkYWN0dWFsX2xhYmVscykKICAgICAgcHJlZGljdGVkID0gdW5wYWNrX2xhYmVscygueCRwcmVkaWN0ZWRfbGFiZWxzKQogICAgICBsaWJyYXJ5X3N0cmF0ZWd5ID0gYWN0dWFsICU+JSAKICAgICAgICBmaWx0ZXIoa2V5ID09ICdMaWJyYXJ5U3RyYXRlZ3knKSAlPiUKICAgICAgICBwdWxsKHZhbHVlKQogICAgICBwbGF0Zm9ybSA9IGFjdHVhbCAlPiUgCiAgICAgICAgZmlsdGVyKGtleSA9PSAnUGxhdGZvcm0nKSAlPiUKICAgICAgICBwdWxsKHZhbHVlKQogICAgICAKICAgICAgdGliYmxlKAogICAgICAgIHNhbXBsZV9pZCA9IC54JHNhbXBsZV9pZCwKICAgICAgICBxdWVyeV9iYXNlcGFpcnMgPSAueCRxdWVyeV9iYXNlcGFpcnMsCiAgICAgICAgcXVlcnlfbWFwcGluZyA9IC54JHF1ZXJ5X21hcHBpbmcsCiAgICAgICAgYWN0dWFsID0gbGlzdChhY3R1YWwpLAogICAgICAgIHByZWRpY3RlZCA9IGxpc3QocHJlZGljdGVkKSwKICAgICAgICBsaWJyYXJ5X3N0cmF0ZWd5ID0gbGlicmFyeV9zdHJhdGVneSwKICAgICAgICBwbGF0Zm9ybSA9IHBsYXRmb3JtCiAgICAgICkKICAgIH0sIC5vcHRpb25zID0gZnVycnJfb3B0aW9ucyhzZWVkID0gVFJVRSkpCiAgCiAgIyBDbG9zZSB0aGUgcGFyYWxsZWwgYmFja2VuZAogIHBsYW4oc2VxdWVudGlhbCkKICAKICByZXR1cm4ocHJvY2Vzc2VkX2RmKQp9CmBgYAoKIyMgY29sbGF0ZV9yZXN1bHRzIGZ1bmN0aW9uClRoaXMgZnVuY3Rpb24gYXBwbGllcyB0aGUgc3VtbWFyaXplX2NvbXBhcmlzb24gZnVuY3Rpb24gdG8gYW4gZW50aXJlIGRhdGFmcmFtZSBvZiBwcmVkaWN0aW9ucy4gSXQgcHJvY2Vzc2VzIGVhY2ggcm93LCBjb21wYXJpbmcgYWN0dWFsIGFuZCBwcmVkaWN0ZWQgbGFiZWxzIGFuZCBjb2xsZWN0aW5nIG1ldGFkYXRhIGxpa2Ugc2FtcGxlIElELCBxdWVyeSBiYXNlIHBhaXJzLCBhbmQgbWFwcGluZyBpbmZvcm1hdGlvbi4gVGhlIGZ1bmN0aW9uIHRoZW4gY29tYmluZXMgYWxsIHRoZXNlIGluZGl2aWR1YWwgc3VtbWFyaWVzIGludG8gYSBzaW5nbGUsIGNvbXByZWhlbnNpdmUgZGF0YWZyYW1lLiBUaGlzIHJlc3VsdGluZyBkYXRhZnJhbWUgcHJvdmlkZXMgYSBkZXRhaWxlZCBvdmVydmlldyBvZiBwcmVkaWN0aW9uIGFjY3VyYWN5IGFjcm9zcyBhbGwgc2FtcGxlcywgaW5jbHVkaW5nIHZhcmlvdXMgbWV0YWRhdGEgZm9yIGVhY2ggY29tcGFyaXNvbi4KYGBge3J9CmNvbGxhdGVfcmVzdWx0cyA8LSBmdW5jdGlvbihwcmVkaWN0aW9uc19kZikgewogIHByZWRpY3Rpb25zX2RmID0gcHJlZGljdGlvbnNfZGYgJT4lCiAgICBzZWxlY3Qoc2FtcGxlX2lkLCBxdWVyeV9iYXNlcGFpcnMsIHF1ZXJ5X21hcHBpbmcsIGFjdHVhbCwgcHJlZGljdGVkLCBwbGF0Zm9ybSwgbGlicmFyeV9zdHJhdGVneSkKICAjIEFwcGx5IHRoZSBzdW1tYXJpemVfY29tcGFyaXNvbiBmdW5jdGlvbiB0byBlYWNoIHJvdwogIHJlc3VsdHNfbGlzdCA8LSBwbWFwKHByZWRpY3Rpb25zX2RmLCBmdW5jdGlvbihzYW1wbGVfaWQsIHF1ZXJ5X2Jhc2VwYWlycywgcXVlcnlfbWFwcGluZywgYWN0dWFsLCBwcmVkaWN0ZWQsIHBsYXRmb3JtLCBsaWJyYXJ5X3N0cmF0ZWd5KSB7CiAgICByZXN1bHQgPC0gc3VtbWFyaXplX2NvbXBhcmlzb24oYWN0dWFsLCBwcmVkaWN0ZWQpCiAgICByZXN1bHQkc2FtcGxlX2lkIDwtIHNhbXBsZV9pZAogICAgcmVzdWx0JHF1ZXJ5X2Jhc2VwYWlycyA8LSBxdWVyeV9iYXNlcGFpcnMKICAgIHJlc3VsdCRxdWVyeV9tYXBwaW5nIDwtIHF1ZXJ5X21hcHBpbmcKICAgIHJlc3VsdCRwbGF0Zm9ybSA9IHBsYXRmb3JtCiAgICByZXN1bHQkbGlicmFyeV9zdHJhdGVneSA9IGxpYnJhcnlfc3RyYXRlZ3kKICAgIHJldHVybihyZXN1bHQpCiAgfSkKICAKICAjIENvbWJpbmUgYWxsIHJlc3VsdHMgaW50byBhIHNpbmdsZSBkYXRhZnJhbWUKICByZXN1bHRzX2RmIDwtIGJpbmRfcm93cyhyZXN1bHRzX2xpc3QpCiAgcmV0dXJuKHJlc3VsdHNfZGYpCn0KYGBgCgoKUmVhZCBkYXRhCmBgYHtyfQp2a19kZiA9IHJlYWRfY3N2KCdhbGxfU1JBX3RheGEvcmVzdWx0c192YXJLb2Rlcy9wcmVkaWN0aW9ucy5jc3YnKSAlPiUgcHJvY2Vzc19kYXRhZnJhbWVfcGFyYWxsZWwKY2dyX2RmID0gIHJlYWRfY3N2KCdhbGxfU1JBX3RheGEvcmVzdWx0c19jZ3JzL3ByZWRpY3Rpb25zLmNzdicpICU+JSBwcm9jZXNzX2RhdGFmcmFtZV9wYXJhbGxlbAoKY2dyX2RmCnZrX2RmCmBgYAoKCmBgYHtyfQp2a19jZ3JfZGYgPSBiaW5kX3Jvd3ModmtfZGYsIGNncl9kZikKc3VtbWFyaWVzID0gY29sbGF0ZV9yZXN1bHRzKHZrX2Nncl9kZikgCnN1bW1hcmllcwpgYGAKCk5vdyBsZXQncyBjYWxjdWxhdGUgbWV0cmljczoKYGBge3J9CnN1bW1hcmllc193aXRoX3Njb3JlcyA8LSBzdW1tYXJpZXMgJT4lCiAgZ3JvdXBfYnkoa2V5LCBxdWVyeV9iYXNlcGFpcnMsIHF1ZXJ5X21hcHBpbmcsIHBsYXRmb3JtLCBsaWJyYXJ5X3N0cmF0ZWd5KSAlPiUKICBzdW1tYXJpemUoCiAgICB0b3RhbF9UUCA9IHN1bShUUCksCiAgICB0b3RhbF9GTiA9IHN1bShGTiksCiAgICB0b3RhbF9GUCA9IHN1bShGUCksCiAgICBOID0gbigpLAogICAgLmdyb3VwcyA9ICdkcm9wJwogICkgJT4lCiAgbXV0YXRlKAogICAgbWljcm9wcmVjaXNpb24gPSB0b3RhbF9UUCAvICh0b3RhbF9UUCArIHRvdGFsX0ZQKSwKICAgIG1pY3JvcmVjYWxsID0gdG90YWxfVFAgLyAodG90YWxfVFAgKyB0b3RhbF9GTiksCiAgICBGMSA9IDIgKiAobWljcm9wcmVjaXNpb24gKiBtaWNyb3JlY2FsbCkgLyAobWljcm9wcmVjaXNpb24gKyBtaWNyb3JlY2FsbCkKICApICU+JQogIHJlcGxhY2VfbmEobGlzdChtaWNyb3ByZWNpc2lvbiA9IDAsIG1pY3JvcmVjYWxsID0gMCwgRjEgPSAwKSkgJT4lCiAgc2VsZWN0KHF1ZXJ5X2Jhc2VwYWlycywgcXVlcnlfbWFwcGluZywga2V5LCBGMSwgbWljcm9wcmVjaXNpb24sIG1pY3JvcmVjYWxsLCBldmVyeXRoaW5nKCkpICU+JQogIGZpbHRlcigKICAgICAgZ3JlcGwoIl5bMTI1XTB7MSx9JCIsIGZvcm1hdChxdWVyeV9iYXNlcGFpcnMsIHNjaWVudGlmaWMgPSBGQUxTRSwgdHJpbSA9IFRSVUUpKQogICkKCnN1bW1hcmllc193aXRoX3Njb3JlcwpgYGAKCgojIFBsb3RzCmBgYHtyfQpwbG90X2RmID0gc3VtbWFyaWVzX3dpdGhfc2NvcmVzICU+JQogIHJlbmFtZSgKICAgIHByZWNpc2lvbiA9IG1pY3JvcHJlY2lzaW9uLAogICAgcmVjYWxsID0gbWljcm9yZWNhbGwKICApICU+JQogIHBpdm90X2xvbmdlcigKICAgIGNvbHMgPSBjKEYxLCBwcmVjaXNpb24sIHJlY2FsbCksCiAgICBuYW1lc190byA9ICJtZXRyaWMiLAogICAgdmFsdWVzX3RvID0gIm1ldHJpY192YWx1ZSIKICApICU+JQogIGZpbHRlcihrZXkgJWluJSBjKCJMaWJyYXJ5U3RyYXRlZ3kiLCAiUGxhdGZvcm0iLCAiVGF4b25vbXlfYWxsIiwgIlRheG9ub215X2ZhbWlseSIsICJUYXhvbm9teV9nZW51cyIsICJUYXhvbm9teV9zcGVjaWVzIikpICU+JQogIG11dGF0ZShxdWVyeV9tYXBwaW5nID0gaWZlbHNlKHF1ZXJ5X21hcHBpbmc9PSdjZ3InLCdyZkNHUicscXVlcnlfbWFwcGluZykpCmBgYAoKClRoaXMgZ3JhcGggc2hvd3MgaG93IHdlbGwgdGhlIHR3byBtZXRob2RzIGJlaGF2ZSB0byByZWNvZ25pemUgdGhlIGxpYnJhcnkgc3RyYXRlZ3k6CmBgYHtyfQpicmtzID0gYyg1MDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwLAogICAgICAgICAgICAgMjAwMDAwMCwKICAgICAgICAgICAgIDUwMDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwMCwKICAgICAgICAgICAgIDIwMDAwMDAwLAogICAgICAgICAgICAgNTAwMDAwMDAsCiAgICAgICAgICAgICAxMDAwMDAwMDAsCiAgICAgICAgICAgICAyMDAwMDAwMDAKICAgICAgICAgICAgICkKCgpwbG90X2RmX3N0cmF0ID0gcGxvdF9kZiAlPiUgZmlsdGVyKGtleSAlaW4lIGMoIkxpYnJhcnlTdHJhdGVneSIpKQoKTnMgPC0gcGxvdF9kZl9zdHJhdCAlPiUKICBncm91cF9ieShwbGF0Zm9ybSwgbGlicmFyeV9zdHJhdGVneSkgJT4lCiAgc3VtbWFyaXplKE4gPSBtYXgoTiksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIG11dGF0ZShOID0gcGFzdGUwKCJOID0gIiwgTikpCgpnZ3Bsb3QocGxvdF9kZl9zdHJhdCwKICAgICAgIGFlcyh4PXF1ZXJ5X2Jhc2VwYWlycywgeT1tZXRyaWNfdmFsdWUsIGNvbG9yPW1ldHJpYyxsaW5ldHlwZT1xdWVyeV9tYXBwaW5nLGdyb3VwPWludGVyYWN0aW9uKG1ldHJpYyxxdWVyeV9tYXBwaW5nKSkpICsKICBnZW9tX2xpbmUoKSArCiAgZmFjZXRfZ3JpZChwbGF0Zm9ybX5saWJyYXJ5X3N0cmF0ZWd5KSArCiAgbGFicyh0aXRsZT0iQWNjdXJhY3kgaW4gcHJlZGljdGluZyB0aGUgbGlicmFyeSBzdHJhdGVneSAoR0JTLCBSQUQgb3IgV0dTKSIsCiAgICAgICB4PSJNZXRyaWMgdmFsdWUiLCAKICAgICAgIHk9IkJhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzIiwKICAgICAgIGNvbG9yPSJNZXRyaWMiLAogICAgICAgbGluZXR5cGU9IkstbWVyIE1hcHBpbmciKSArCiAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZV9jdXQgPSBzY2FsZXM6OmN1dF9zaSgnYnAnKSksYnJlYWtzID0gYnJrcykgICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSwgYnJlYWtzPWMoMCwwLjIsMC40LDAuNiwwLjgsMSkpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoaGp1c3Q9MSxhbmdsZT00NSkpICsKICAgZ2VvbV9sYWJlbChkYXRhID0gTnMsIGFlcyhsYWJlbCA9IE4sIHggPSAxMjAwMDAwMCwgeSA9IDAuMSksCiAgICAgICAgICAgICBoanVzdCA9IDAuNSwgdmp1c3QgPSAwLjUsCiAgICAgICAgICAgICBsYWJlbC5wYWRkaW5nID0gdW5pdCgwLjIsICJsaW5lcyIpLAogICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDAuMiwKICAgICAgICAgICAgIHNpemU9MiwKICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwKICAgICAgICAgICAgIGZpbGwgPSAid2hpdGUiLAogICAgICAgICAgICAgaW5oZXJpdC5hZXMgPSBGQUxTRSkKCnNhZmVfZ2dzYXZlICgnaW1hZ2VzX21hbnVzY3JpcHQvc3VwcF9zdHJhdF9wcmVkaWN0aW9uLnBkZicsd2lkdGg9NyxoZWlnaHQgPSA3LHVzZURpbmdiYXRzPUYpCmBgYAoKVGhpcyBncmFwaCBzaG93cyBob3cgd2VsbCB0aGUgdHdvIG1ldGhvZHMgYmVoYXZlIHRvIHJlY29nbml6ZSB0aGUgc2VxdWVuY2luZyBwbGF0Zm9ybToKYGBge3J9CmdncGxvdChwbG90X2RmICU+JSBmaWx0ZXIoa2V5ICVpbiUgYygiUGxhdGZvcm0iKSksCiAgICAgICBhZXMoeD1xdWVyeV9iYXNlcGFpcnMsIHk9bWV0cmljX3ZhbHVlLCBjb2xvcj1tZXRyaWMsbGluZXR5cGU9cXVlcnlfbWFwcGluZyxncm91cD1pbnRlcmFjdGlvbihtZXRyaWMscXVlcnlfbWFwcGluZyxrZXkpKSkgKwogIGdlb21fbGluZSgpICsKICBmYWNldF9ncmlkKHBsYXRmb3JtfmxpYnJhcnlfc3RyYXRlZ3kpICsKICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKHNjYWxlX2N1dCA9IHNjYWxlczo6Y3V0X3NpKCdicCcpKSxicmVha3MgPSBicmtzKSAgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpLCBicmVha3M9YygwLDAuMiwwLjQsMC42LDAuOCwxKSkgKwogIGxhYnModGl0bGU9IkFjY3VyYWN5IGluIHByZWRpY3Rpbmcgc2VxdWVuY2luZyBwbGF0Zm9ybSIsCiAgICAgICB4PSJNZXRyaWMgdmFsdWUiLCAKICAgICAgIHk9IkJhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzIiwKICAgICAgIGNvbG9yPSJNZXRyaWMiLAogICAgICAgbGluZXR5cGU9IkstbWVyIE1hcHBpbmciKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGhqdXN0PTEsYW5nbGU9NDUpKSArCiAgIGdlb21fbGFiZWwoZGF0YSA9IE5zLCBhZXMobGFiZWwgPSBOLCB4ID0gMTIwMDAwMDAsIHkgPSAwLjEpLAogICAgICAgICAgICAgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41LAogICAgICAgICAgICAgbGFiZWwucGFkZGluZyA9IHVuaXQoMC4yLCAibGluZXMiKSwKICAgICAgICAgICAgIGxhYmVsLnNpemUgPSAwLjIsCiAgICAgICAgICAgICBzaXplPTIsCiAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsCiAgICAgICAgICAgICBmaWxsID0gIndoaXRlIiwKICAgICAgICAgICAgIGluaGVyaXQuYWVzID0gRkFMU0UpCgpzYWZlX2dnc2F2ZSAoJ2ltYWdlc19tYW51c2NyaXB0L3N1cHBfcGxhdGZvcm1fcHJlZGljdGlvbi5wZGYnLHdpZHRoPTcsaGVpZ2h0ID0gNyx1c2VEaW5nYmF0cz1GKQpgYGAKClRoaXMgZ3JhcGggc2hvd3MgaG93IHdlbGwgdGhlIHR3byBtZXRob2RzIGJlaGF2ZSBmb3IgZGlmZmVyZW50IHRheG9ub21pYyBsZXZlbHM6CmBgYHtyfQp0YXhfbGFiZWxzID0gYygiYWxsIHJhbmtzIiwgImZhbWlseSIsICJnZW51cyIsICJzcGVjaWVzIikKbmFtZXModGF4X2xhYmVscykgPSBjKCJUYXhvbm9teV9hbGwiLCAiVGF4b25vbXlfZmFtaWx5IiwgIlRheG9ub215X2dlbnVzIiwgIlRheG9ub215X3NwZWNpZXMiKQoKcGxvdF9kZl90YXggPSBwbG90X2RmICU+JSAKICBmaWx0ZXIoa2V5ICVpbiUgYygiVGF4b25vbXlfYWxsIiwgIlRheG9ub215X2ZhbWlseSIsICJUYXhvbm9teV9nZW51cyIsICJUYXhvbm9teV9zcGVjaWVzIikpICU+JQogIG11dGF0ZShrZXkgPSB0YXhfbGFiZWxzW2tleV0sCiAgICAgICAgIHBsYXRmb3JtID0gaWZlbHNlKHBsYXRmb3JtPT0nT1hGT1JEX05BTk9QT1JFJywnTkFOT1BPUkUnLHBsYXRmb3JtKSkKCk5zIDwtIHBsb3RfZGZfdGF4ICAlPiUKICBncm91cF9ieShwbGF0Zm9ybSwgbGlicmFyeV9zdHJhdGVneSxrZXkpICU+JQogIHN1bW1hcml6ZShOID0gbWF4KE4pLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUKICBtdXRhdGUoTiA9IHBhc3RlMCgiTiA9ICIsIE4pKQoKZ2dwbG90KHBsb3RfZGZfdGF4LAogICAgICAgYWVzKHg9cXVlcnlfYmFzZXBhaXJzLCAKICAgICAgICAgICB5PW1ldHJpY192YWx1ZSwgY29sb3I9bWV0cmljLAogICAgICAgICAgIGxpbmV0eXBlPXF1ZXJ5X21hcHBpbmcsCiAgICAgICAgICAgZ3JvdXA9aW50ZXJhY3Rpb24obWV0cmljLHF1ZXJ5X21hcHBpbmcsa2V5KSkpICsKICBnZW9tX2xpbmUoKSArCiAgZmFjZXRfZ3JpZChrZXl+cGxhdGZvcm0rbGlicmFyeV9zdHJhdGVneSkgKyAgIAogIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGVfY3V0ID0gc2NhbGVzOjpjdXRfc2koJ2JwJykpLGJyZWFrcyA9IGJya3MpICArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMSksIGJyZWFrcz1jKDAsMC4yLDAuNCwwLjYsMC44LDEpKSArCiAgZ2VvbV9sYWJlbChkYXRhID0gTnMsIGFlcyhsYWJlbCA9IE4sIHggPSA3MDAwMDAwLCB5ID0gMC4xKSwKICAgICAgICAgICAgIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwKICAgICAgICAgICAgIGxhYmVsLnBhZGRpbmcgPSB1bml0KDAuMiwgImxpbmVzIiksCiAgICAgICAgICAgICBsYWJlbC5zaXplID0gMC4yLAogICAgICAgICAgICAgc2l6ZT0yLAogICAgICAgICAgICAgY29sb3IgPSAiZ3JheTMwIiAsCiAgICAgICAgICAgICBmaWxsID0gImdyYXk5NiIsCiAgICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgbGFicyh0aXRsZT0iQWNjdXJhY3kgaW4gcHJlZGljdGluZyB0YXhvbm9teSIsCiAgICAgICB5PSJNZXRyaWMgdmFsdWUiLCAKICAgICAgIHg9IkJhc2UgcGFpcnMgaW4gcXVlcnkgaW1hZ2VzIiwKICAgICAgIGNvbG9yPSJNZXRyaWMiLAogICAgICAgbGluZXR5cGU9IkstbWVyIE1hcHBpbmciKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9NyksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoaGp1c3Q9MSxhbmdsZT00NSksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScpCmBgYApGb3IgdGhlIG1hbnVzY3JpcHQsIGxldCdzIHNpbXBsaWZ5IGFuZCBvbmx5IHNob3cgcmZDR1I6CmBgYHtyfQpnZ3Bsb3QocGxvdF9kZl90YXggJT4lIGZpbHRlcihxdWVyeV9tYXBwaW5nPT0ncmZDR1InLG1ldHJpYyAhPSAiRjEiKSwKICAgICAgIGFlcyh4PXF1ZXJ5X2Jhc2VwYWlycywgCiAgICAgICAgICAgeT1tZXRyaWNfdmFsdWUsIGNvbG9yPW1ldHJpYywKICAgICAgICAgICBncm91cD1tZXRyaWMpKSArCiAgZ2VvbV9saW5lKCkgKwogIGZhY2V0X2dyaWQoa2V5fnBsYXRmb3JtK2xpYnJhcnlfc3RyYXRlZ3kpICsgICAKICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKHNjYWxlX2N1dCA9IHNjYWxlczo6Y3V0X3NpKCdicCcpKSxicmVha3MgPSBicmtzKSAgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpLCBicmVha3M9YygwLDAuMiwwLjQsMC42LDAuOCwxKSkgKwogIGdlb21fbGFiZWwoZGF0YSA9IE5zLCBhZXMobGFiZWwgPSBOLCB4ID0gNzAwMDAwMCwgeSA9IDAuMSksCiAgICAgICAgICAgICBoanVzdCA9IDAuNSwgdmp1c3QgPSAwLjUsCiAgICAgICAgICAgICBsYWJlbC5wYWRkaW5nID0gdW5pdCgwLjIsICJsaW5lcyIpLAogICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDAuMiwKICAgICAgICAgICAgIHNpemU9MiwKICAgICAgICAgICAgIGNvbG9yID0gImdyYXkzMCIgLAogICAgICAgICAgICAgZmlsbCA9ICJncmF5OTYiLAogICAgICAgICAgICAgaW5oZXJpdC5hZXMgPSBGQUxTRSkgKwogIGxhYnModGl0bGU9IkFjY3VyYWN5IGluIHByZWRpY3RpbmcgdGF4b25vbXkiLAogICAgICAgeT0iTWV0cmljIHZhbHVlIiwgCiAgICAgICB4PSJCYXNlIHBhaXJzIGluIHF1ZXJ5IGltYWdlcyIsCiAgICAgICBjb2xvcj0iTWV0cmljIiwKICAgICAgIGxpbmV0eXBlPSJLLW1lciBNYXBwaW5nIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTcpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGhqdXN0PTEsYW5nbGU9NDUpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nKQoKc2FmZV9nZ3NhdmUgKCdpbWFnZXNfbWFudXNjcmlwdC9maWdfWF90YXhvbm9teV9wcmVkaWN0aW9uLnBkZicsd2lkdGg9NyxoZWlnaHQgPSA1LHVzZURpbmdiYXRzPUYpCmBgYApMZXQncyBub3cgdHJ5IHRvIHVuZGVyc3RhbmQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG51bWJlciBvZiBzYW1wbGVzIGF2YWlsYWJsZSBmb3IgYSBsYWJlbCBhbmQgaXRzIGFjY3VyYWN5LiBGb3IgZWFjaCBsYWJlbCwgd2Ugd2lsbCBzdW1tYXJpemUgdGhlIE4gaW4gdHJhaW5pbmcgc2V0LCBwcmVjaXNpb24sIHJlY2FsbCBhbmQgRjEgc2NvcmUgaW4gdGhlIHZhbGlkYXRpb24gc2V0LiBUaGVuIHdlIHdpbGwgcGxvdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbnVtYmVyIG9mIHRyYWluaW5nIHNhbXBsZXMgYW5kIHZhbGlkYXRpb24gbWV0cmljcy4KCmBgYHtyfQpzdW1tYXJpemVfY29tcGFyaXNvbl93aXRoX3ZhbHVlIDwtIGZ1bmN0aW9uKGFjdHVhbCwgcHJlZGljdGVkKSB7CiAgCiAgYWN0dWFsIDwtIGFjdHVhbCAlPiUgbXV0YXRlKGtfdiA9IHBhc3RlKGtleSwgdmFsdWUsIHNlcCA9ICI6IikpCiAgcHJlZGljdGVkIDwtIHByZWRpY3RlZCAlPiUgbXV0YXRlKCBrX3YgPSBwYXN0ZShrZXksIHZhbHVlLCBzZXAgPSAiOiIpKQogIAogICMgR2V0IHRoZSB1bmlxdWUga2V5cyBmcm9tIGJvdGggYWN0dWFsIGFuZCBwcmVkaWN0ZWQsIGV4Y2x1ZGluZyBzcGVjaWZpYyBrZXlzCiAgYWxsX2t2IDwtIHVuaXF1ZShjKGFjdHVhbCRrX3YsIHByZWRpY3RlZCRrX3YpKQogIAogIAogICMgSW5pdGlhbGl6ZSBhIHJlc3VsdCBkYXRhZnJhbWUKICByZXN1bHQgPC0gZGF0YS5mcmFtZSgKICAgIGtfdiA9IGFsbF9rdiwKICAgIE5fYWN0dWFsID0gaW50ZWdlcihsZW5ndGgoYWxsX2t2KSksCiAgICBOX3ByZWRpY3RlZCA9IGludGVnZXIobGVuZ3RoKGFsbF9rdikpLAogICAgVFAgPSBpbnRlZ2VyKGxlbmd0aChhbGxfa3YpKSwKICAgIEZOID0gaW50ZWdlcihsZW5ndGgoYWxsX2t2KSksCiAgICBGUCA9IGludGVnZXIobGVuZ3RoKGFsbF9rdikpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQogIAogIGZvciAoa192IGluIGFsbF9rdikgewogICAgYWN0dWFsX3ZhbHVlcyA8LSBhY3R1YWwkdmFsdWVbYWN0dWFsJGtfdiA9PSBrX3ZdCiAgICBwcmVkaWN0ZWRfdmFsdWVzIDwtIHByZWRpY3RlZCR2YWx1ZVtwcmVkaWN0ZWQka192ID09IGtfdl0KICAgIAogICAgTl9hY3R1YWwgPC0gbGVuZ3RoKGFjdHVhbF92YWx1ZXMpCiAgICBOX3ByZWRpY3RlZCA8LSBsZW5ndGgocHJlZGljdGVkX3ZhbHVlcykKICAgIFRQIDwtIHN1bShhY3R1YWxfdmFsdWVzICVpbiUgcHJlZGljdGVkX3ZhbHVlcykKICAgIEZOIDwtIHN1bSghYWN0dWFsX3ZhbHVlcyAlaW4lIHByZWRpY3RlZF92YWx1ZXMpCiAgICBGUCA8LSBzdW0oIXByZWRpY3RlZF92YWx1ZXMgJWluJSBhY3R1YWxfdmFsdWVzKQogICAgCiAgICByZXN1bHRbcmVzdWx0JGtfdiA9PSBrX3YsIF0gPC0gbGlzdChrX3YsIE5fYWN0dWFsLCBOX3ByZWRpY3RlZCwgVFAsIEZOLCBGUCkKICB9CiAgCiAgcmV0dXJuKHJlc3VsdCkKfQpgYGAKCmBgYHtyfQpjb2xsYXRlX3Jlc3VsdHNfd2l0aF92YWx1ZSA8LSBmdW5jdGlvbihwcmVkaWN0aW9uc19kZikgewogIHByZWRpY3Rpb25zX2RmID0gcHJlZGljdGlvbnNfZGYgJT4lCiAgICBzZWxlY3Qoc2FtcGxlX2lkLCBxdWVyeV9iYXNlcGFpcnMsIHF1ZXJ5X21hcHBpbmcsIGFjdHVhbCwgcHJlZGljdGVkLCBwbGF0Zm9ybSwgbGlicmFyeV9zdHJhdGVneSkKICAjIEFwcGx5IHRoZSBzdW1tYXJpemVfY29tcGFyaXNvbiBmdW5jdGlvbiB0byBlYWNoIHJvdwogIHJlc3VsdHNfbGlzdCA8LSBwbWFwKHByZWRpY3Rpb25zX2RmLCBmdW5jdGlvbihzYW1wbGVfaWQsIHF1ZXJ5X2Jhc2VwYWlycywgcXVlcnlfbWFwcGluZywgYWN0dWFsLCBwcmVkaWN0ZWQsIHBsYXRmb3JtLCBsaWJyYXJ5X3N0cmF0ZWd5KSB7CiAgICByZXN1bHQgPC0gc3VtbWFyaXplX2NvbXBhcmlzb25fd2l0aF92YWx1ZShhY3R1YWwsIHByZWRpY3RlZCkKICAgIHJlc3VsdCRzYW1wbGVfaWQgPC0gc2FtcGxlX2lkCiAgICByZXN1bHQkcXVlcnlfYmFzZXBhaXJzIDwtIHF1ZXJ5X2Jhc2VwYWlycwogICAgcmVzdWx0JHF1ZXJ5X21hcHBpbmcgPC0gcXVlcnlfbWFwcGluZwogICAgcmVzdWx0JHBsYXRmb3JtID0gcGxhdGZvcm0KICAgIHJlc3VsdCRsaWJyYXJ5X3N0cmF0ZWd5ID0gbGlicmFyeV9zdHJhdGVneQogICAgcmV0dXJuKHJlc3VsdCkKICB9KQogIAogICMgQ29tYmluZSBhbGwgcmVzdWx0cyBpbnRvIGEgc2luZ2xlIGRhdGFmcmFtZQogIHJlc3VsdHNfZGYgPC0gYmluZF9yb3dzKHJlc3VsdHNfbGlzdCkKICByZXR1cm4ocmVzdWx0c19kZikKfQpgYGAKCmBgYHtyfQpzdW1tYXJpZXNfa3YgPSBjb2xsYXRlX3Jlc3VsdHNfd2l0aF92YWx1ZSh2a19jZ3JfZGYpCnN1bW1hcmllc19rdgpgYGAKYGBge3J9Cm1ldHJpY3NfYnlfbGFiZWwgPSBzdW1tYXJpZXNfa3YgJT4lCiAgc2VsZWN0KC1zYW1wbGVfaWQsLXF1ZXJ5X2Jhc2VwYWlycykgJT4lCiAgZ3JvdXBfYnkoa192KSAlPiUKICBzdW1tYXJpc2UoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCBzdW0pKSAlPiUKICBtdXRhdGUoCiAgICBtaWNyb3ByZWNpc2lvbiA9IFRQIC8gKFRQICsgRlApLAogICAgbWljcm9yZWNhbGwgPSBUUCAvIChUUCArIEZOKSwKICAgIEYxID0gMiAqIChtaWNyb3ByZWNpc2lvbiAqIG1pY3JvcmVjYWxsKSAvIChtaWNyb3ByZWNpc2lvbiArIG1pY3JvcmVjYWxsKQogICkgJT4lCiAgcmVwbGFjZV9uYShsaXN0KG1pY3JvcHJlY2lzaW9uID0gMCwgbWljcm9yZWNhbGwgPSAwLCBGMSA9IDApKQogIAptZXRyaWNzX2J5X2xhYmVsCmBgYAoKTm93IHdlIG9ubHkgbmVlZCB0byBjb3VudCB0aGUgb2NjdXJyZW5jZXMgb2YgZWFjaCBsYWJlbCBpbiB0aGUgdHJhaW5pbmcgc2V0CmBgYHtyfQptZXRyaWNzX2J5X2xhYmVsID0gcmVhZF9jc3YoJ2FsbF9TUkFfdGF4YS9sYWJlbHNfTUxfdHJhaW5pbmcuY3N2JykgJT4lCiAgZmlsdGVyKCFzYW1wbGUgJWluJSB2a19jZ3JfZGYkc2FtcGxlX2lkKSAlPiUKICBwdWxsKGxhYmVscykgJT4lCiAgc3RyX3NwbGl0KCc7JykgJT4lCiAgdW5saXN0ICU+JQogIHRhYmxlICU+JQogIGFzX3RpYmJsZShjb2x1bW5fbmFtZT0na192JyxuPSJOX3RyYWluaW5nIikgJT4lCiAgcmVuYW1lKGtfdiA9ICIuIikgJT4lCiAgcmlnaHRfam9pbihtZXRyaWNzX2J5X2xhYmVsKSAlPiUKICBwaXZvdF9sb25nZXIobWljcm9wcmVjaXNpb246RjEsbmFtZXNfdG8gPSAnbWV0cmljJyx2YWx1ZXNfdG8gPSAndmFsdWUnKSAlPiUKICBhcnJhbmdlKE5fdHJhaW5pbmcpIAoKbWV0cmljc19ieV9sYWJlbApgYGAKYGBge3J9Cm1ldHJpY3Nfc3VtbWFyeSA8LSBtZXRyaWNzX2J5X2xhYmVsICU+JQogIGZpbHRlcihtZXRyaWMgIT0nRjEnKSAlPiUKICAjIENyZWF0ZSBsb2ctc2NhbGVkIGJpbnMKICBtdXRhdGUoYmluID0gY3V0KGxvZzEwKE5fdHJhaW5pbmcpLCBicmVha3MgPSAzMCkpICU+JQogIGdyb3VwX2J5KGJpbiwgbWV0cmljKSAlPiUKICBzdW1tYXJpc2UoCiAgICBtZWRpYW4gPSBtZWRpYW4odmFsdWUpLAogICAgcTI1ID0gcXVhbnRpbGUodmFsdWUsIDAuMjUpLAogICAgcTc1ID0gcXVhbnRpbGUodmFsdWUsIDAuNzUpLAogICAgIyBHZXQgbWlkZGxlIG9mIHRoZSBiaW4gZm9yIHBsb3R0aW5nCiAgICB4ID0gbWVkaWFuKE5fdHJhaW5pbmcpLAogICAgTj1uKCkKICApCm1ldHJpY3Nfc3VtbWFyeSAKCmdncGxvdCgpICsKICBnZW9tX2JpbjJkKGRhdGEgPSBtZXRyaWNzX2J5X2xhYmVsICU+JSBmaWx0ZXIobWV0cmljICE9J0YxJyksIGFlcyh4ID0gTl90cmFpbmluZywgeSA9IHZhbHVlKSkgKwogICNnZW9tX3JpYmJvbihkYXRhID0gbWV0cmljc19zdW1tYXJ5LAogICMgICAgICAgICAgICBhZXMoeCA9IHgsIHltaW4gPSBxMjUsIHltYXggPSBxNzUpLAogICMgICAgICAgICAgIGFscGhhID0gMC4yLAogICMgICAgICAgICAgICBmaWxsID0gImJsdWUiKSArCiAgZ2VvbV9saW5lKGRhdGEgPSBtZXRyaWNzX3N1bW1hcnksCiAgICAgICAgICAgIGFlcyh4ID0geCwgeSA9IG1lZGlhbiksCiAgICAgICAgICAgIGNvbG9yID0gIm9yYW5nZSIsCiAgICAgICAgICAgIGxpbmV3aWR0aCA9IDEpICsKICAgICNnZW9tX3BvaW50KGRhdGEgPSBtZXRyaWNzX2J5X2xhYmVsICU+JSBmaWx0ZXIobWV0cmljICE9J0YxJyksIAogICAgIyAgICAgICAgICBhZXMoeCA9IE5fdHJhaW5pbmcsIHkgPSB2YWx1ZSksCiAgICAjICAgICAgICAgIGFscGhhID0gMC4xKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyh0cmFucz0ibG9nIixuYW1lPSJMYWJlbCBjb3VudHMiLGJyZWFrcz1jKDEsMywxMCwzMCwxMDAsMzAwLDEwMDApKSArCiAgZmFjZXRfd3JhcCh+bWV0cmljLCBsYWJlbGxlciA9IGxhYmVsbGVyKG1ldHJpYz1jKCdtaWNyb3ByZWNpc2lvbic9J1ByZWNpc2lvbicsJ21pY3JvcmVjYWxsJz0nUmVjYWxsJykpKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgVHJhaW5pbmcgRXhhbXBsZXMgKGxvZyBzY2FsZSkiLAogICAgeSA9ICJWYWx1ZSIsCiAgICB0aXRsZSA9ICJWYWxpZGF0aW9uIE1ldHJpY3MgYnkgVHJhaW5pbmcgU2V0IFNpemUiCiAgKQoKc2FmZV9nZ3NhdmUgKCdpbWFnZXNfbWFudXNjcmlwdC9maWdfWF9uX3RyYWluLnBkZicsd2lkdGg9NyxoZWlnaHQgPSAyLjUsdXNlRGluZ2JhdHM9RikKYGBgCgoKCgoKCgoKTGV0J3Mgbm93IGdldCBzb21lIG51bWJlcnMgZm9yIHB1YmxpY2F0aW9uLiBXaGF0IGlzIHRoZSBhdmVyYWdlIHByZWNpc2lvbiBhdCBzcGVjaWVzIGxldmVsPwpgYGB7cn0KcGxvdF9kZiAlPiUKICBmaWx0ZXIoa2V5PT0nVGF4b25vbXlfc3BlY2llcycsCiAgICAgICAgIG1ldHJpYyAlaW4lIGMoJ3ByZWNpc2lvbicsJ3JlY2FsbCcpLAogICAgICAgICBxdWVyeV9tYXBwaW5nPT0ndmFyS29kZScpICU+JQogIGdyb3VwX2J5KG1ldHJpYyxwbGF0Zm9ybSkgJT4lCiAgc3VtbWFyaXNlKG1lYW5fdmFsdWU9bWVhbihtZXRyaWNfdmFsdWUpKQogIApgYGAKCkdlbnVzIGxldmVsCmBgYHtyfQpwbG90X2RmICU+JQogIGZpbHRlcihrZXk9PSdUYXhvbm9teV9nZW51cycsCiAgICAgICAgIG1ldHJpYyAlaW4lIGMoJ3ByZWNpc2lvbicsJ3JlY2FsbCcpLAogICAgICAgICBxdWVyeV9tYXBwaW5nPT0ndmFyS29kZScpICU+JQogIGdyb3VwX2J5KG1ldHJpYyxwbGF0Zm9ybSkgJT4lCiAgc3VtbWFyaXNlKG1lYW5fdmFsdWU9bWVhbihtZXRyaWNfdmFsdWUpKQogIApgYGAKCkZhbWlseSBsZXZlbApgYGB7cn0KcGxvdF9kZiAlPiUKICBmaWx0ZXIoa2V5PT0nVGF4b25vbXlfZmFtaWx5JywKICAgICAgICAgbWV0cmljICVpbiUgYygncHJlY2lzaW9uJywncmVjYWxsJyksCiAgICAgICAgIHF1ZXJ5X21hcHBpbmc9PSd2YXJLb2RlJykgJT4lCiAgZ3JvdXBfYnkobWV0cmljLHBsYXRmb3JtKSAlPiUKICBzdW1tYXJpc2UobWVhbl92YWx1ZT1tZWFuKG1ldHJpY192YWx1ZSkpCmBgYAoKQWxsIHRheG9ub215CmBgYHtyfQpwbG90X2RmICU+JQogIGZpbHRlcihrZXk9PSdUYXhvbm9teV9hbGwnLAogICAgICAgICBtZXRyaWMgJWluJSBjKCdwcmVjaXNpb24nLCdyZWNhbGwnKSwKICAgICAgICAgcXVlcnlfbWFwcGluZz09J3ZhcktvZGUnKSAlPiUKICBncm91cF9ieShtZXRyaWMscGxhdGZvcm0pICU+JQogIHN1bW1hcmlzZShtZWFuX3ZhbHVlPW1lYW4obWV0cmljX3ZhbHVlKSkKYGBgCgoKCgojIEdlbmVyYXRpbmcgbnVtYmVycyBmb3IgcHVibGljYXRpb24KCkhlcmUgd2UganVzdCBxdWVyeSBvdXIgcmVzdWx0cyB0byBnZXQgYSBmZXcgZmlndXJlcyB0aGF0IHdlIHJlcG9ydCBpbiB0aGUgcGFwZXIuCgpUb3RhbCBudW1iZXIgb2Ygc2FtcGxlcyB1c2VkIGluIGNyb3NzLXZhbGlkYXRpb246CmBgYHtyfQpkaW0oc2FtcF9sYWJlbHMpCmBgYAoKTnVtYmVyIG9mIFN0aWdtYXBoeWxsb24gc2FtcGxlcyB3aXRoIGVhY2gga2luZCBvZiBlcnJvciBmb3IgdmFya29kZXI6CmBgYHtyfQpzdW1tYXJ5X3NwZWNpZXMKYGBgCgpOdW1iZXIgb2YgU3RpZ21hcGh5bGxvbiBzYW1wbGVzIHdpdGggZWFjaCBraW5kIG9mIGVycm9yIGZvciBza21lcjoKYGBge3J9CnNrbWVyX3N1bW1hcnlfc3BlY2llcwpgYGAKVHJhZGl0aW9uYWwgYmFyY29kZSBhY2N1cmFjeSBmb3Igc3BlY2llczoKYGBge3J9CmJhcmNvZGVfc3VtbWFyeV9zcGVjaWVzICU+JSBhcnJhbmdlKHF1ZXJ5X2JwLG1hcmtlcikKYGBgCkNvbmNhdGVuYXRlZCBiYXJjb2RlIGFjY3VyYWN0IGZvciBzcGVjaWVzOgpgYGB7cn0KY29uY2F0X3N1bW1hcnlfc3BlY2llcwpgYGAKCnZhcktvZGVyIGFjY3VyYWN5IGZvciBnZW5lcmE6CmBgYHtyfQpzdW1tYXJ5X2dlbnVzCmBgYAp2YXJLb2RlciBhY2N1cmFjeSBmb3IgZmFtaWx5OgpgYGB7cn0Kc3VtbWFyeV9mYW1pbHkKYGBgCgpTa21lciBhY2N1cmFjeSBmb3IgZ2VuZXJhOgpgYGB7cn0Kc2ttZXJfc3VtbWFyeV9nZW51cwpgYGAKClNrbWVyIGFjY3VyYWN5IGZvciBmYW1pbHk6CmBgYHtyfQpza21lcl9zdW1tYXJ5X2ZhbWlseQpgYGAKCk51bWJlciBvZiBzYW1wbGVzIGF2YWlsYWJsZSBmb3IgZWFjaCBnZW51cyBhbmQgZGF0YSBhbW91bnQKYGBge3J9CnJlc3VsdHMgJT4lCiAgbXV0YXRlKGdlbnVzID0gc3RyX2V4dHJhY3QoYWN0dWFsX2xhYmVscywiKD88PWdlbnVzOilbXjtdKyIpKSAlPiUKICBncm91cF9ieShxdWVyeV9icCkgJT4lCiAgc3VtbWFyaXplKE49bigpKSAlPiUKICBjb21wbGV0ZSgpCmBgYAoKUGxvdCBudW1iZXIgb2Ygc2FtcGxlcyBmb3Igc3VwcGxlbWVudGFyeSBtYXRlcmlhbC4KCmBgYHtyfQpuX3NhbXBsZXNfZ2VuZXJhID0gcmVzdWx0cyAlPiUKICBtdXRhdGUodGF4b24gPSBzdHJfZXh0cmFjdChhY3R1YWxfbGFiZWxzLCIoPzw9Z2VudXM6KVteO10rIikpICU+JQogIGdyb3VwX2J5KHRheG9uLCBxdWVyeV9icCkgJT4lCiAgc3VtbWFyaXplKE49bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgY29tcGxldGUodGF4b24sIHF1ZXJ5X2JwLCBmaWxsID0gbGlzdChOPTApKSAlPiUKICBtdXRhdGUodGF4b24gPSBmY3RfcmVvcmRlcih0YXhvbiwgTikpCm5fc2FtcGxlc19nZW5lcmEgCgpuX3NhbXBsZXNfc3BlY2llcyA9IHJlc3VsdHMgJT4lCiAgbXV0YXRlKHRheG9uID0gc3RyX2V4dHJhY3QoYWN0dWFsX2xhYmVscywiKD88PXNwZWNpZXM6KVteO10rIikpICU+JQogIGZpbHRlcighaXMubmEodGF4b24pKSAlPiUKICBncm91cF9ieSh0YXhvbiwgcXVlcnlfYnApICU+JQogIHN1bW1hcml6ZShOPW4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGNvbXBsZXRlKHRheG9uLCBxdWVyeV9icCwgZmlsbCA9IGxpc3QoTj0wKSkgICU+JQogIG11dGF0ZSh0YXhvbiA9IGZjdF9yZW9yZGVyKHRheG9uLCBOKSkKbl9zYW1wbGVzX3NwZWNpZXMgCmBgYAoKRm9yIFNSQSBldWthcnlvdGVzLCB3ZSBoYXZlIHRvIGNvdW50IGJvdGggdmFsaWRhdGlvbiBhbmQgdHJhaW5pbmcgc2FtcGxlcywgc2luY2Ugd2UgZGlkIG5vdCBkbyBjcm9zcy12YWxpZGF0aW9uLiBMZXQncyB1c2UgaW1hZ2UgbmFtZXMgdG8gZ2V0IHRoZSBpbmZvcm1hdGlvbiBhbmQgdGhlbiB0aGUgcmVzdWx0cyB0YWJsZSB0byBmaWd1cmUgb3V0IHdoaWNoIG9uZXMgd2VyZSBpbiB0aGUgdmFsaWRhdGlvbiBzZXQuCmBgYHtyfQphbGxfZmlsZXMgPSBjKGxpc3QuZmlsZXMoJ2FsbF9TUkFfZXVrYXJ5b3RlX2ZhbWlsaWVzL3ZhcmtvZGVyX2ltYWdlc19TUkEvJyxwYXR0ZXJuPScqLnBuZycscmVjdXJzaXZlID0gVCksCiAgICAgICAgICAgICAgbGlzdC5maWxlcygnYWxsX1NSQV9ldWthcnlvdGVfZmFtaWxpZXN2YXJrb2Rlcl9xdWVyeV9pbWFnZXMvJyxwYXR0ZXJuPScqLnBuZycscmVjdXJzaXZlID0gVCkpCgpuX3NhbXBsZXNfU1JBID0gZGF0YS5mcmFtZShmaWxlbmFtZT1hbGxfZmlsZXMpICU+JQogIG11dGF0ZSgKICAgIHNhbXBsZV9pZCA9IHN0cl9leHRyYWN0KGZpbGVuYW1lLCAiXiguKykoPz1AKSIpLCAgIyBDYXB0dXJlIGV2ZXJ5dGhpbmcgdXAgdG8gdGhlICJAIiBzeW1ib2wgYnV0IGV4Y2x1ZGUgdGhlIHN5bWJvbCBpdHNlbGYKICAgIHF1ZXJ5X2JwID0gc3RyX2V4dHJhY3QoZmlsZW5hbWUsICIoPzw9QCkoWzAtOV0rKUsiKSAjIG11bHRpcGx5IGJ5IDEwMDAgdG8gY29udmVydCBLIHRvIHRoZSBhY3R1YWwgbnVtYmVyCiAgKSAlPiUgCiAgbGVmdF9qb2luKHJlYWRfY3N2KCdhbGxfU1JBX2V1a2FyeW90ZV9mYW1pbGllcy9ydW5zX3RvX2Rvd25sb2FkX2RhdGEuY3N2JykgJT4lIAogICAgICAgICAgICAgIHNlbGVjdChzYW1wbGVfaWQ9UnVuLEtpbmdkb20sdGF4b249RmFtaWx5SUQpKSAlPiUKICBtdXRhdGVfYXQodmFycyh0YXhvbiksYXMuY2hhcmFjdGVyKSAlPiUKICBtdXRhdGUodmFsaWRhdGlvbl9zZXQgPSBzYW1wbGVfaWQgJWluJSB2YXJLb2Rlcl9TUkFfcmVzdWx0cyRzYW1wbGVfaWQpICU+JQogIGdyb3VwX2J5KHRheG9uLCBxdWVyeV9icCx2YWxpZGF0aW9uX3NldCkgJT4lCiAgc3VtbWFyaXplKE49bigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHRheG9uID0gZmN0X3Jlb3JkZXIodGF4b24sIE4pKQoKbl9zYW1wbGVzX1NSQQogIAoKICAoKGxpc3QuZmlsZXMoJ2FsbF9TUkFfZXVrYXJ5b3RlX2ZhbWlsaWVzdmFya29kZXJfaW1hZ2VzX1NSQS8nLHBhdHRlcm49JyoucG5nJyxyZWN1cnNpdmUgPSBUKSAlPiUKICAgICAgc3RyX2V4dHJhY3QoIl4oLispKD89QCkiKSklaW4lCnZhcktvZGVyX1NSQV9yZXN1bHRzJHNhbXBsZV9pZCkgJT4lIHN1bW1hcnkKYGBgCgoKCmBgYHtyfQpwbG90X05zYW1wbGVzX2FyZWEgPSBmdW5jdGlvbihkZiwgdGl0bGUpewogIGRmID0gZGYgIyU+JSAKICAgIyBtdXRhdGUocXVlcnlfYnAgPSBwYXJzZV9udW1iZXIocXVlcnlfYnApICoxMDAwKQogIAogIG5fbGV2ZWxzIDwtIGxlbmd0aCh1bmlxdWUoZGYkdGF4b24pKQogIHZpcmlkaXNfY29sb3JzIDwtIHZpcmlkaXM6OnR1cmJvKG5fbGV2ZWxzKQogIAogIGhhbGZfbiA8LSBjZWlsaW5nKG5fbGV2ZWxzIC8gMikKICByZW9yZGVyZWRfY29sb3JzIDwtIGMocmJpbmQodmlyaWRpc19jb2xvcnNbMTpoYWxmX25dLCB2aXJpZGlzX2NvbG9yc1soaGFsZl9uICsgMSk6bl9sZXZlbHNdKSkKCgogIAogIAogIGdncGxvdChkZiwgYWVzKHg9cXVlcnlfYnAseT1OLGZpbGw9dGF4b24sIGNvbG9yID0gdGF4b24sIGdyb3VwID0gdGF4b24pKSArCiAgICBnZW9tX2FyZWEocG9zaXRpb249IHBvc2l0aW9uX3N0YWNrKCkpICsKICAgICNnZW9tX2xpbmUocG9zaXRpb249J3N0YWNrJykgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcmVvcmRlcmVkX2NvbG9ycywgCiAgICAgICAgICAgICAgICAgICAgICBhZXN0aGV0aWNzID0gYygnY29sb3VyJywnZmlsbCcpLAogICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSAnbm9uZScpICsKICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGVfY3V0ID0gc2NhbGVzOjpjdXRfc2koJ2JwJykpLAogICAgICAgICAgICAgICAgICBicmVha3MgPSB1bmlxdWUobl9zYW1wbGVzX2dlbmVyYSRxdWVyeV9icCksCiAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IHJhbmdlKHVuaXF1ZShuX3NhbXBsZXNfZ2VuZXJhJHF1ZXJ5X2JwKSkpICsKICAgIHNjYWxlX3lfY29udGludW91cyhuLmJyZWFrcyA9IDEwLCBtaW5vcl9icmVha3MgPSB3YWl2ZXIoKSkgKwogICAgZ2d0aXRsZSh0aXRsZSkgKwogICAgeWxhYignTnVtYmVyIG9mIHNhbXBsZXMnKSArCiAgICB4bGFiKCdCYXNlIHBhaXJzIGluIHF1ZXJ5IGltYWdlcycpICsKICAgIHRoZW1lX2ZldygpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGhqdXN0PTEsYW5nbGU9NDUpLAogICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLAogICAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JheSgwLjUpKSwKICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9IGdyYXkoMC42KSxsaW5ldHlwZSA9IDIpLAogICAgICAgICAgICBwYW5lbC5vbnRvcCA9IFRSVUUpCn0KYGBgCgpgYGB7cn0KCk5fc3BlY2llcyA9IHBsb3RfTnNhbXBsZXNfYXJlYShuX3NhbXBsZXNfc3BlY2llcywgdGl0bGUgPSBleHByZXNzaW9uKGl0YWxpYygnU3RpZ21hcGh5bGxvbicpfidzcGVjaWVzJykpICsgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkKTl9nZW5lcmEgPSBwbG90X05zYW1wbGVzX2FyZWEobl9zYW1wbGVzX2dlbmVyYSwgdGl0bGUgPSAnTWFscGlnaGlhbGVzIGdlbmVyYScpICsgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkKCnAgPSBwbG90X2dyaWQoTl9zcGVjaWVzLCBOX2dlbmVyYSwgbnJvdyA9IDEpCgojIEFkZCBjb21tb24gcGxvdCB0aXRsZSB3aXRoIHdoaXRlIGJhY2tncm91bmQKY29tbW9uX3RpdGxlID0gZ2dkcmF3KCkgKyBkcmF3X2xhYmVsKCJOdW1iZXIgb2Ygc2FtcGxlcyBhdmFpbGFibGUgZm9yIGRpZmZlcmVudCBkYXRhIGFtb3VudHMiLCBmb250ZmFjZSA9ICdib2xkJywgeCA9IDAuNSwgaGp1c3QgPSAwLjUpICsgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJ3aGl0ZSIpLCBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAwLCAwLCAwKSwgIm51bGwiKSkKcCA9IHBsb3RfZ3JpZChjb21tb25fdGl0bGUsIHAsIG5jb2wgPSAxLCByZWxfaGVpZ2h0cyA9IGMoMC4xLCAxKSkKCiMgQWRkIGNvbW1vbiBYLWF4aXMgdGl0bGUgd2l0aCB3aGl0ZSBiYWNrZ3JvdW5kCnhfYXhpc190aXRsZSA9IGdnZHJhdygpICsgZHJhd19sYWJlbCgiUG9zdC1jbGVhbmluZyBiYXNlIHBhaXJzIGF2YWlsYWJsZSIsIHggPSAwLjUsIGhqdXN0ID0gMC41LCB2anVzdCA9IDEpICsgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJ3aGl0ZSIpLCBwbG90Lm1hcmdpbiA9IHVuaXQoYygtMSwgMCwgMCwgMCksICJsaW5lcyIpKQpwID0gcGxvdF9ncmlkKHAsIHhfYXhpc190aXRsZSwgbmNvbCA9IDEsIHJlbF9oZWlnaHRzID0gYygxLCAwLjEpKQoKCnByaW50KHApCgpzYWZlX2dnc2F2ZSAoJ2ltYWdlc19tYW51c2NyaXB0L3N1cHBfZmlnX25fc2FtcGxlcy5wZGYnLCB3aWR0aD04LGhlaWdodCA9IDQpCnNhZmVfZ2dzYXZlICgnaW1hZ2VzX21hbnVzY3JpcHQvc3VwcF9maWdfbl9zYW1wbGVzLnBuZycsIHdpZHRoPTgsaGVpZ2h0ID0gNCxkcGkgPSAxMjAwKQoKYGBgCgpUb3RhbCBudW1iZXIgb2YgU1JBIGV1a2FyeW90ZSBmYW1pbHkgc2FtcGxlcy4gVmFsaWRhdGlvbjoKYGBge3J9CnJlYWRfY3N2KCdhbGxfU1JBX2V1a2FyeW90ZV9mYW1pbGllcy92YXJrb2Rlcl90cmFpbmVkX21vZGVsX01ML2lucHV0X2RhdGEuY3N2JylbLTFdICU+JQogIGdyb3VwX2J5KGlzX3ZhbGlkKSAlPiUKICBzdW1tYXJpc2UoTiA9IG4oKSkKYGBgCgpGb3IgU1JBIGFsbCB0YXhhLCBsZXQncyBzdW1tYXJpemUgdGhlIG51bWJlciBvZiBzYW1wbGVzIGZvciBlYWNoIGxhYmVsLiBMZXQncyBzdGFydCBieSBwYXJzaW5nIHRoZSBkYXRhLiBXZSB3aWxsIHN0YXJ0IHdpdGggdHJhaW5fdmFsaWRfc2V0cy5jc3YgYnV0IHRoZW4gZmlsdGVyIHRvIHRoZSBzYW1wbGVzIGFjdHVhbGx5IHVzZWQuIEEgZmV3IG1pZ2h0IGhhdmUgZmFpbGVkIHRvIHByb2R1Y2UgdmFyS29kZXMuCgpgYGB7cn0KcGxhbihtdWx0aXNlc3Npb24sIHdvcmtlcnMgPSAxMCkKCgphbGxfU1JBX3NhbXBsZXMgPSByZWFkX2NzdigiYWxsX1NSQV90YXhhL3RyYWluX3ZhbGlkX3NldHMuY3N2IikgJT4lCiAgc3BsaXQoMTpucm93KC4pKSAlPiUgICMgU3BsaXQgdGhlIGRhdGFmcmFtZSBpbnRvIGEgbGlzdCBvZiByb3dzCiAgZnV0dXJlX21hcF9kZnIoIH4gewogICAgYWN0dWFsID0gdW5wYWNrX2xhYmVscygueCRsYWJlbHMpCiAgICBsaWJyYXJ5X3N0cmF0ZWd5ID0gYWN0dWFsICU+JQogICAgICBmaWx0ZXIoa2V5ID09ICdMaWJyYXJ5U3RyYXRlZ3knKSAlPiUKICAgICAgcHVsbCh2YWx1ZSkKICAgIHBsYXRmb3JtID0gYWN0dWFsICU+JQogICAgICBmaWx0ZXIoa2V5ID09ICdQbGF0Zm9ybScpICU+JQogICAgICBwdWxsKHZhbHVlKQogICAgCiAgICB0aWJibGUoCiAgICAgIHNhbXBsZV9pZCA9IC54JFJ1biwKICAgICAgbGFiZWxzID0gbGlzdChhY3R1YWwpLAogICAgICBsaWJyYXJ5X3N0cmF0ZWd5ID0gbGlicmFyeV9zdHJhdGVneSwKICAgICAgcGxhdGZvcm0gPSBwbGF0Zm9ybSwKICAgICAgaXNfdmFsaWQgPSAueCRpc192YWxpZAogICAgKQogIH0sIC5vcHRpb25zID0gZnVycnJfb3B0aW9ucyhzZWVkID0gVFJVRSkKICApCgpwbGFuKHNlcXVlbnRpYWwpCgphY3R1YWxfdHIgPSByZWFkX2NzdigiYWxsX1NSQV90YXhhL3ZhcmtvZGVzX1ZpVF9NTC9pbnB1dF9kYXRhLmNzdiIpICAlPiUgZmlsdGVyKGlzX3ZhbGlkPT1GQUxTRSkgJT4lIHB1bGwoc2FtcGxlKSAlPiUgdW5pcXVlCmFjdHVhbF92ZCA9IHJlYWRfY3N2KCJhbGxfU1JBX3RheGEvcmVzdWx0c192YXJLb2Rlcy9wcmVkaWN0aW9ucy5jc3YiKSAlPiUgcHVsbChzYW1wbGVfaWQpICU+JSB1bmlxdWUKCmFsbF9TUkFfc2FtcGxlcyA9IGFsbF9TUkFfc2FtcGxlcyU+JSBmaWx0ZXIoc2FtcGxlX2lkICVpbiUgYyhhY3R1YWxfdHIsYWN0dWFsX3ZkKSkKYWxsX1NSQV9zYW1wbGVzCgpgYGAKCk5vdyBsZXQncyBjb3VudCB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgZm9yIGVhY2ggbGFiZWwKYGBge3J9Ck5fc3VtbWFyeSA9IGFsbF9TUkFfc2FtcGxlcyAlPiUKICB1bm5lc3QobGFiZWxzKSAlPiUKICByZW5hbWUobGFiZWxfa2V5ID0ga2V5LCBsYWJlbF92YWx1ZSA9IHZhbHVlKSAlPiUKICBncm91cF9ieShpc192YWxpZCxsaWJyYXJ5X3N0cmF0ZWd5LHBsYXRmb3JtLGxhYmVsX2tleSxsYWJlbF92YWx1ZSkgJT4lCiAgc3VtbWFyaXplKE5fc2FtcGxlcyA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgZmlsdGVyKGxhYmVsX2tleSAlaW4lIGMoJ1RheG9ub215X2ZhbWlseScsJ1RheG9ub215X3NwZWNpZXMnKSkKCk5fc3VtbWFyeQpgYGAKCk5vdywgZm9yIGVhY2ggY29tYmluYXRpb24gb2YgbGlicmFyeSBzdHJhdGVneSwgcGxhdGZvcm0gYW5kIHdoZXRoZXIgc2FtcGxlIGlzIHZhbGlkYXRpb24sIHdlIHdpbGwgc3VtbWFyaXplIHRoZSByYW5nZSwgbWVhbiBhbmQgbWVkaWFuIG51bWJlciBvZiBzYW1wbGVzIHBlciBsYWJlbC4KCmBgYHtyfQpOX3Blcl9sYWJlbF9rZXkgPSBOX3N1bW1hcnkgJT4lCiAgZ3JvdXBfYnkoaXNfdmFsaWQsIHBsYXRmb3JtLCBsaWJyYXJ5X3N0cmF0ZWd5LCBsYWJlbF9rZXkpICU+JQogIHN1bW1hcmlzZSgKICAgIG5fbGFiZWxzID0gbigpLAogICAgbWluID0gbWluKE5fc2FtcGxlcywgbmEucm0gPSBUUlVFKSwKICAgIHExID0gcXVhbnRpbGUoTl9zYW1wbGVzLCAwLjI1LCBuYS5ybSA9IFRSVUUpLAogICAgbWVkaWFuID0gbWVkaWFuKE5fc2FtcGxlcywgbmEucm0gPSBUUlVFKSwKICAgIG1lYW4gPSBtZWFuKE5fc2FtcGxlcywgbmEucm0gPSBUUlVFKSwKICAgIHEzID0gcXVhbnRpbGUoTl9zYW1wbGVzLCAwLjc1LCBuYS5ybSA9IFRSVUUpLAogICAgbWF4ID0gbWF4KE5fc2FtcGxlcywgbmEucm0gPSBUUlVFKQogICkgJT4lCiAgdW5ncm91cCgpCgpOX3Blcl9sYWJlbF9rZXkKCmRpci5jcmVhdGUoJ3RhYmxlc19tYW51c2NyaXB0JyxzaG93V2FybmluZ3MgPSBGKQp3cml0ZV9jc3YoTl9wZXJfbGFiZWxfa2V5LCAndGFibGVzX21hbnVzY3JpcHQvc3VtbWFyeV9zYW1wbGVzX2FsbF9TUkEuY3N2JykKYGBgCkxldCdzIG5vdyBjb3VudCB0aGUgdG90YWwgbnVtYmVyIG9mIHVuaXF1ZSB0YXhvbm9teSBsYWJlbHM6CmBgYHtyfQpOX3N1bW1hcnkgJT4lIAogIGZpbHRlcihzdHJfZGV0ZWN0KGxhYmVsX2tleSwnXlRheG9ub215JykpICU+JQogIHB1bGwobGFiZWxfdmFsdWUpICU+JQogIHVuaXF1ZSAlPiUKICBsZW5ndGgoKQpgYGAKCgpBbmQgdGhlIG51bWJlciBvZiB1bmlxdWUgYWNjZXNzaW9uczoKYGBge3J9CmFsbF9TUkFfc2FtcGxlcyRzYW1wbGVfaWQgJT4lIHVuaXF1ZSAlPiUgbGVuZ3RoKCkKYGBgCgoKCiMjIFByZWNpc2lvbiBhbmQgcmVjYWxsCiMjIyBhbGwgU1JBCgpQb29sZWQgYWNyb3NzIHNlcXVlbmNpbmcgbWV0aG9kcyBmb3IgZmFtaWxpZXMgYW5kIGFsbApgYGB7cn0Kc3VtbWFyaWVzX3dpdGhfc2NvcmVzICU+JSBmaWx0ZXIoa2V5ICVpbiUgYygnVGF4b25vbXlfZmFtaWx5JywnVGF4b25vbXlfYWxsJykpICU+JSBncm91cF9ieShrZXkpICU+JQogIHN1bW1hcmlzZShtaW5fcHI9bWluKG1pY3JvcHJlY2lzaW9uKSwKICAgICAgICAgICAgbWF4X3ByPW1heChtaWNyb3ByZWNpc2lvbiksCiAgICAgICAgICAgIG1pbl9yZWM9bWluKG1pY3JvcmVjYWxsKSwKICAgICAgICAgICAgbWF4X3JlYz1tYXgobWljcm9yZWNhbGwpKQpgYGAKRGlzY3JpbWluYXRpbmcgc2VxdWVuY2luZyBtZXRob2RzIGZvciBnZW5lcmEgYW5kIHNwZWNpZXMKYGBge3J9CnN1bW1hcmllc193aXRoX3Njb3JlcyAlPiUgCiAgZmlsdGVyKGtleSAlaW4lIGMoJ1RheG9ub215X2dlbnVzJywnVGF4b25vbXlfc3BlY2llcycpLAogICAgICAgICBxdWVyeV9iYXNlcGFpcnMgPT0gMTAwMDAwMDApICU+JSAKICBhcnJhbmdlKGtleSxkZXNjKG1pY3JvcHJlY2lzaW9uKSkKYGBgCgpOb3cgbGV0J3MgY29tcGFyZSBhdmVyYWdlcyBhY3Jvc3MgbWFwcGluZyBmb3Igc3BlY2llcyBsZXZlbC4KYGBge3J9CnN1bW1hcmllc193aXRoX3Njb3JlcyAlPiUgCiAgZmlsdGVyKGtleSAlaW4lIGMoJ1RheG9ub215X3NwZWNpZXMnKSkgJT4lIAogIGdyb3VwX2J5KHF1ZXJ5X21hcHBpbmcpICU+JQogIHN1bW1hcmlzZShtZWFuKG1pY3JvcHJlY2lzaW9uKSwgbWVhbihtaWNyb3JlY2FsbCkpCmBgYAoKCgojIyMgT3RoZXIgZGF0YXNldHMKQ2FsY3VsYXRlIG1pY3JvLWF2ZXJhZ2VkIHByZWNpc2lvbiBhbmQgcmVjYWxsLgoKYGBge3J9CgpjYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCA9IGZ1bmN0aW9uKHJlc3VsdHMsIHRheG9ub21pY19sZXZlbD1OVUxMKSB7CiAgIyBGdW5jdGlvbiB0byBmaWx0ZXIgbGFiZWxzIGJ5IHRheG9ub21pYyBsZXZlbAogIGZpbHRlcl9sYWJlbHMgPC0gZnVuY3Rpb24obGFiZWxzX2xpc3QsIGxldmVsKSB7CiAgICBpZiAoaXMubnVsbChsZXZlbCkpIHsKICAgICAgcmV0dXJuKGxhYmVsc19saXN0KQogICAgfSBlbHNlIHsKICAgICAgcmV0dXJuKGdyZXAocGFzdGUwKCJeIiwgbGV2ZWwsICI6IiksIGxhYmVsc19saXN0LCB2YWx1ZSA9IFRSVUUpKQogICAgfQogIH0KCiAgIyBGaWx0ZXIgcm93cyBhbmQgbGFiZWxzIGZvciBhIGdpdmVuIHRheG9ub21pYyBsZXZlbAogIGZpbHRlcl9yb3dzX2FuZF9sYWJlbHMgPC0gZnVuY3Rpb24ocmVzdWx0cywgbGV2ZWwpIHsKICAgIGlmIChpcy5udWxsKGxldmVsKSkgewogICAgICByZXR1cm4ocmVzdWx0cykKICAgIH0gZWxzZSB7CiAgICAgICMgS2VlcCBvbmx5IHJvd3Mgd2hlcmUgdGhlIGxldmVsIGlzIGZvdW5kIGluIHF1ZXJ5X2xhYmVscwogICAgICBmaWx0ZXJlZF9yZXN1bHRzIDwtIHJlc3VsdHNbc2FwcGx5KHJlc3VsdHMkcXVlcnlfbGFiZWxzLCBmdW5jdGlvbih4KSB7CiAgICAgICAgYW55KGdyZXBsKHBhc3RlMCgiXiIsIGxldmVsLCAiOiIpLCB4KSkKICAgICAgfSksIF0KCiAgICAgICMgRmlsdGVyIGxhYmVscyBpbiBib3RoIHF1ZXJ5X2xhYmVscyBhbmQgcHJlZGljdGVkX2xpc3QKICAgICAgZmlsdGVyZWRfcmVzdWx0cyRxdWVyeV9sYWJlbHMgPC0gbGFwcGx5KGZpbHRlcmVkX3Jlc3VsdHMkcXVlcnlfbGFiZWxzLCBmaWx0ZXJfbGFiZWxzLCBsZXZlbCkKICAgICAgZmlsdGVyZWRfcmVzdWx0cyRwcmVkaWN0ZWRfbGlzdCA8LSBsYXBwbHkoZmlsdGVyZWRfcmVzdWx0cyRwcmVkaWN0ZWRfbGlzdCwgZmlsdGVyX2xhYmVscywgbGV2ZWwpCgogICAgICByZXR1cm4oZmlsdGVyZWRfcmVzdWx0cykKICAgIH0KICB9CgogICMgQXBwbHkgZmlsdGVyaW5nCiAgZmlsdGVyZWRfcmVzdWx0cyA8LSBmaWx0ZXJfcm93c19hbmRfbGFiZWxzKHJlc3VsdHMsIHRheG9ub21pY19sZXZlbCkKCiAgIyBJbml0aWFsaXplIGNvdW50ZXJzIGZvciB0cnVlIHBvc2l0aXZlcywgZmFsc2UgcG9zaXRpdmVzLCBhbmQgZmFsc2UgbmVnYXRpdmVzCiAgdG90YWxfdHJ1ZV9wb3NpdGl2ZXMgPC0gMAogIHRvdGFsX2ZhbHNlX3Bvc2l0aXZlcyA8LSAwCiAgdG90YWxfZmFsc2VfbmVnYXRpdmVzIDwtIDAKCiAgIyBQcm9jZXNzIGVhY2ggcm93IGluIHRoZSBmaWx0ZXJlZCByZXN1bHRzCiAgZm9yIChpIGluIHNlcV9sZW4obnJvdyhmaWx0ZXJlZF9yZXN1bHRzKSkpIHsKICAgIHF1ZXJ5X2xhYmVscyA8LSBmaWx0ZXJlZF9yZXN1bHRzJHF1ZXJ5X2xhYmVsc1tbaV1dCiAgICBwcmVkaWN0ZWRfbGFiZWxzIDwtIGZpbHRlcmVkX3Jlc3VsdHMkcHJlZGljdGVkX2xpc3RbW2ldXQoKICAgIHRydWVfcG9zaXRpdmVzIDwtIHN1bShwcmVkaWN0ZWRfbGFiZWxzICVpbiUgcXVlcnlfbGFiZWxzKQogICAgZmFsc2VfcG9zaXRpdmVzIDwtIHN1bSghcHJlZGljdGVkX2xhYmVscyAlaW4lIHF1ZXJ5X2xhYmVscyAmICFpcy5uYShwcmVkaWN0ZWRfbGFiZWxzKSAmIHByZWRpY3RlZF9sYWJlbHMgIT0gIiIpCiAgICBmYWxzZV9uZWdhdGl2ZXMgPC0gc3VtKCFxdWVyeV9sYWJlbHMgJWluJSBwcmVkaWN0ZWRfbGFiZWxzICYgIWlzLm5hKHF1ZXJ5X2xhYmVscykgJiBxdWVyeV9sYWJlbHMgIT0gIiIpCgogICAgIyBVcGRhdGUgYWdncmVnYXRlIGNvdW50cwogICAgdG90YWxfdHJ1ZV9wb3NpdGl2ZXMgPC0gdG90YWxfdHJ1ZV9wb3NpdGl2ZXMgKyB0cnVlX3Bvc2l0aXZlcwogICAgdG90YWxfZmFsc2VfcG9zaXRpdmVzIDwtIHRvdGFsX2ZhbHNlX3Bvc2l0aXZlcyArIGZhbHNlX3Bvc2l0aXZlcwogICAgdG90YWxfZmFsc2VfbmVnYXRpdmVzIDwtIHRvdGFsX2ZhbHNlX25lZ2F0aXZlcyArIGZhbHNlX25lZ2F0aXZlcwogIH0KCiAgIyBDYWxjdWxhdGUgbWljcm8tYXZlcmFnZWQgcHJlY2lzaW9uIGFuZCByZWNhbGwKICBtaWNyb19wcmVjaXNpb24gPC0gaWZlbHNlKCh0b3RhbF90cnVlX3Bvc2l0aXZlcyArIHRvdGFsX2ZhbHNlX3Bvc2l0aXZlcykgPiAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3RydWVfcG9zaXRpdmVzIC8gKHRvdGFsX3RydWVfcG9zaXRpdmVzICsgdG90YWxfZmFsc2VfcG9zaXRpdmVzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBOQV9yZWFsXykKICBtaWNyb19yZWNhbGwgPC0gaWZlbHNlKCh0b3RhbF90cnVlX3Bvc2l0aXZlcyArIHRvdGFsX2ZhbHNlX25lZ2F0aXZlcykgPiAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3RydWVfcG9zaXRpdmVzIC8gKHRvdGFsX3RydWVfcG9zaXRpdmVzICsgdG90YWxfZmFsc2VfbmVnYXRpdmVzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBOQV9yZWFsXykKCiAgcmV0dXJuKHRpYmJsZSh0YXhvbm9taWNfbGV2ZWwgPSB0YXhvbm9taWNfbGV2ZWwsIG1pY3JvX3ByZWNpc2lvbiA9IG1pY3JvX3ByZWNpc2lvbiwgbWljcm9fcmVjYWxsID0gbWljcm9fcmVjYWxsKSkKfQoKYGBgCgoKIyMjIyBNYWxwaWdoaWFsZXMKUHJlY2lzaW9uIGFuZCByZWNhbGwgZm9yIHNwZWNpZXM6CgpgYGB7cn0KZmlsdGVyKHJlc3VsdHMsc3RyX2RldGVjdChhY3R1YWxfbGFiZWxzLCdzcGVjaWVzJykpICU+JQogICAgICAgICBzcGxpdCguJHF1ZXJ5X2JwKSAlPiUKICAgICAgICAgbWFwX2Rmcih+Y2FsY3VsYXRlX3ByZWNpc2lvbl9yZWNhbGwoLngsJ3NwZWNpZXMnKSwuaWQ9J3F1ZXJ5X2JwJykKYGBgCgpQcmVjaXNpb24gYW5kIHJlY2FsbCBmb3IgZ2VuZXJhOgoKYGBge3J9CmZpbHRlcihyZXN1bHRzLHN0cl9kZXRlY3QoYWN0dWFsX2xhYmVscywnZ2VudXMnKSkgJT4lCiAgICAgICAgIHNwbGl0KC4kcXVlcnlfYnApICU+JQogICAgICAgICBtYXBfZGZyKH5jYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCgueCwnZ2VudXMnKSwuaWQ9J3F1ZXJ5X2JwJykKYGBgCgpQcmVjaXNpb24gYW5kIHJlY2FsbCBmb3IgZmFtaWxpZXM6CgpgYGB7cn0KZmlsdGVyKHJlc3VsdHMsc3RyX2RldGVjdChhY3R1YWxfbGFiZWxzLCdmYW1pbHknKSkgJT4lCiAgICAgICAgIHNwbGl0KC4kcXVlcnlfYnApICU+JQogICAgICAgICBtYXBfZGZyKH5jYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCgueCwnZmFtaWx5JyksLmlkPSdxdWVyeV9icCcpCmBgYAoKIyMjIyBTUkEgZXVrYXJ5b3RlcwpQcmVjaXNpb24gYW5kIHJlY2FsbCBmb3IgU1JBIGV1a2FyeW90ZSBmYW1pbGllcywgcmZDR1IgcmVwcmVzZW50YXRpb246CmBgYHtyfQpwcl9yY19ldWtfU1JBID0gdmFyS29kZXJfU1JBX3Jlc3VsdHMgJT4lCiAgc3BsaXQoLiRxdWVyeV9icCkgJT4lCiAgICAgICAgIG1hcF9kZnIofmNhbGN1bGF0ZV9wcmVjaXNpb25fcmVjYWxsKC54KSwuaWQ9J3F1ZXJ5X2JwJykgJT4lCiAgbXV0YXRlKHF1ZXJ5X2JwID0gMTAwMCooc3RyX3JlbW92ZShxdWVyeV9icCwnSycpICU+JSBhcy5udW1lcmljKSkKcHJfcmNfZXVrX1NSQQpgYGAKClByZWNpc2lvbiBhbmQgcmVjYWxsIGZvciBTUkEgZXVrYXJ5b3RlIGZhbWlsaWVzLCB2YXJLb2RlIHJlcHJlc2VudGF0aW9uOgpgYGB7cn0KcHJfcmNfZXVrX1NSQV92ayA9IHZhcktvZGVyX1NSQV9yZXN1bHRzX3ZrICU+JQogIHNwbGl0KC4kcXVlcnlfYnApICU+JQogICAgICAgICBtYXBfZGZyKH5jYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCgueCksLmlkPSdxdWVyeV9icCcpICU+JQogIG11dGF0ZShxdWVyeV9icCA9IDEwMDAqYXMuaW50ZWdlcihxdWVyeV9icCkpCnByX3JjX2V1a19TUkFfdmsKYGBgCkxldCdzIGpvaW4gYm90aCB0YWJsZXM6CmBgYHtyfQpmdWxsX2pvaW4ocHJfcmNfZXVrX1NSQSxwcl9yY19ldWtfU1JBX3ZrLGJ5PSJxdWVyeV9icCIsc3VmZml4ID0gYygnLnZhcktvZGUnLCcucmZDR1InKSkKYGBgCgoKUHJlY2lzaW9uIGFuZCByZWNhbGwgZm9yIG90aGVyIHRheGEsIHZhcktvZGUgcmVwcmVzZW50YXRpb246CmBgYHtyfQpvdGhlcl9yZXN1bHRzICU+JQogIGZpbHRlcihpbl90cmFpbmluZ19tb2RlbCA9PSAneWVzJykgJT4lCiAgc3BsaXQoLiRkYXRhc2V0KSAlPiUKICBtYXBfZGZyKH5jYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCgueCksLmlkPSdkYXRhc2V0JykKYGBgCgpQcmVjaXNpb24gYW5kIHJlY2FsbCBmb3Igb3RoZXIgdGF4YSwgQ0dSIHJlcHJlc2VudGF0aW9uOgpgYGB7cn0Kb3RoZXJfcmVzdWx0c19DR1IgJT4lCiAgZmlsdGVyKGluX3RyYWluaW5nX21vZGVsID09ICd5ZXMnKSAlPiUKICBzcGxpdCguJGRhdGFzZXQpICU+JQogIG1hcF9kZnIofmNhbGN1bGF0ZV9wcmVjaXNpb25fcmVjYWxsKC54KSwuaWQ9J2RhdGFzZXQnKQpgYGAKCk51bWJlcnMgY29ycmVjdCBhbmQgaWNvcnJlY3QsIENHUjoKYGBge3J9CnN1bW1hcnlfb3RoZXJzX0NHUiAlPiUgZ3JvdXBfYnkoZGF0YXNldCxyZXN1bHQsdGF4b25faW5fdHJhaW5pbmcpICU+JSBzdW1tYXJpc2UoTj1zdW0oTikscD1tZWFuKHApKSAKYGBgCgoKUHJlY2lzaW9uIGFuZCByZWNhbGwgZm9yIGNvbnZlbnRpb25hbCBiYXJjb2RlcyBhdCBzcGVjaWVzIGxldmVsCmBgYHtyfQpyZXN1bHRzX2JhcmNvZGVzICU+JQogIGdyb3VwX2J5KG1hcmtlcixxdWVyeV9icCkgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZShwcmVjaXNpb25fcmVjYWxsID0gcHVycnI6Om1hcChkYXRhLCBjYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCwnc3BlY2llcycpKSAlPiUKICBzZWxlY3QoLWRhdGEpICU+JQogIHVubmVzdChwcmVjaXNpb25fcmVjYWxsKQpgYGAKUHJlY2lzaW9uIGFuZCByZWNhbGwgZm9yIGNvbmNhdGVuYXRlZCBjb252ZW50aW9uYWwgYmFyY29kZXMgYXQgc3BlY2llcyBsZXZlbApgYGB7cn0KcmVzdWx0c19jb25jYXRfYmFyY29kZXMgJT4lCiAgc3BsaXQoLiRxdWVyeV9icCkgJT4lCiAgbWFwX2Rmcih+Y2FsY3VsYXRlX3ByZWNpc2lvbl9yZWNhbGwoLngpLC5pZD0ncXVlcnlfYnAnLCdzcGVjaWVzJykKYGBgCgpQcmVjaXNpb24vcmVjYWxsIGZvciBza21lciBhdCBzcGVjaWVzIGxldmVsLiBQcmVjaXNpb24gYW5kIHJlY2FsbCBhcmUgbm90IGFsd2F5cyB0aGUgc2FtZSBiZWNhdXNlIGluIHNvbWUgY2FzZXMgc2ttZXIgcHJlZGljdGVkIHRoZSB3cm9uZyBnZW51cywgYW5kLCB0aGVyZWZvcmUsIHRoZXJlIHdhcyBubyBzcGVjaWVzIHByZWRpY3Rpb24uCgpgYGB7cn0Kc2ttZXJfcmVzdWx0c19kZiAlPiUKICBzcGxpdCguJHF1ZXJ5X2JwKSAlPiUKICBtYXBfZGZyKH5jYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCgueCwnc3BlY2llcycpLC5pZD0ncXVlcnlfYnAnKQpgYGAKCgpQcmVjaXNpb24gYW5kIHJlY2FsbCBmb3IgY29udmVudGlvbmFsIGJhcmNvZGVzIGF0IGdlbnVzIGxldmVsCmBgYHtyfQpyZXN1bHRzX2JhcmNvZGVzICU+JQogIGdyb3VwX2J5KG1hcmtlcixxdWVyeV9icCkgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZShwcmVjaXNpb25fcmVjYWxsID0gcHVycnI6Om1hcChkYXRhLCBjYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCwnZ2VudXMnKSkgJT4lCiAgc2VsZWN0KC1kYXRhKSAlPiUKICB1bm5lc3QocHJlY2lzaW9uX3JlY2FsbCkKYGBgClByZWNpc2lvbiBhbmQgcmVjYWxsIGZvciBjb25jYXRlbmF0ZWQgY29udmVudGlvbmFsIGJhcmNvZGVzIGF0IGdlbnVzIGxldmVsCmBgYHtyfQpyZXN1bHRzX2NvbmNhdF9iYXJjb2RlcyAlPiUKICBzcGxpdCguJHF1ZXJ5X2JwKSAlPiUKICBtYXBfZGZyKH5jYWxjdWxhdGVfcHJlY2lzaW9uX3JlY2FsbCgueCksLmlkPSdxdWVyeV9icCcsJ2dlbnVzJykKYGBgCgpQcmVjaXNpb24vcmVjYWxsIGZvciBza21lciBhdCBnZW51cyBsZXZlbDoKCmBgYHtyfQpza21lcl9yZXN1bHRzX2RmICU+JQogIHNwbGl0KC4kcXVlcnlfYnApICU+JQogIG1hcF9kZnIofmNhbGN1bGF0ZV9wcmVjaXNpb25fcmVjYWxsKC54LCdnZW51cycpLC5pZD0ncXVlcnlfYnAnKQpgYGAKCgoKUHJlY2lzaW9uIGFuZCByZWNhbGwgZm9yIGNvbnZlbnRpb25hbCBiYXJjb2RlcyBhdCBmYW1pbHkgbGV2ZWwKYGBge3J9CnJlc3VsdHNfYmFyY29kZXMgJT4lCiAgZ3JvdXBfYnkobWFya2VyLHF1ZXJ5X2JwKSAlPiUKICBuZXN0KCkgJT4lCiAgbXV0YXRlKHByZWNpc2lvbl9yZWNhbGwgPSBwdXJycjo6bWFwKGRhdGEsIGNhbGN1bGF0ZV9wcmVjaXNpb25fcmVjYWxsLCdmYW1pbHknKSkgJT4lCiAgc2VsZWN0KC1kYXRhKSAlPiUKICB1bm5lc3QocHJlY2lzaW9uX3JlY2FsbCkKYGBgClByZWNpc2lvbiBhbmQgcmVjYWxsIGZvciBjb25jYXRlbmF0ZWQgY29udmVudGlvbmFsIGJhcmNvZGVzIGF0IGZhbWlseSBsZXZlbApgYGB7cn0KcmVzdWx0c19jb25jYXRfYmFyY29kZXMgJT4lCiAgc3BsaXQoLiRxdWVyeV9icCkgJT4lCiAgbWFwX2Rmcih+Y2FsY3VsYXRlX3ByZWNpc2lvbl9yZWNhbGwoLngpLC5pZD0ncXVlcnlfYnAnLCdmYW1pbHknKQpgYGAKClByZWNpc2lvbi9yZWNhbGwgZm9yIHNrbWVyIGF0IGZhbWlseSBsZXZlbDoKCmBgYHtyfQpza21lcl9yZXN1bHRzX2RmICU+JQogIHNwbGl0KC4kcXVlcnlfYnApICU+JQogIG1hcF9kZnIofmNhbGN1bGF0ZV9wcmVjaXNpb25fcmVjYWxsKC54LCdmYW1pbHknKSwuaWQ9J3F1ZXJ5X2JwJykKYGBgCg==